From 0573359ac7e2c74b1e1e60e2087756d31ec854ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 08:59:30 -0500 Subject: [PATCH 01/21] Bump org.junit.jupiter:junit-jupiter from 5.11.3 to 5.11.4 (#4984) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index bd66202f29..507d65b282 100644 --- a/build.gradle +++ b/build.gradle @@ -688,8 +688,8 @@ dependencies { testImplementation 'commons-validator:commons-validator:1.9.0' testImplementation 'org.springframework.kafka:spring-kafka-test:3.3.0' testImplementation "org.springframework:spring-beans:${spring_version}" - testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.3' + testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' testImplementation('org.awaitility:awaitility:4.2.2') { exclude(group: 'org.hamcrest', module: 'hamcrest') } From 4e30396bedecb9e9e04ee33e0453156bbad5eb67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:00:41 +0100 Subject: [PATCH 02/21] Bump com.nimbusds:nimbus-jose-jwt from 9.47 to 9.48 (#4983) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 507d65b282..2c71116d09 100644 --- a/build.gradle +++ b/build.gradle @@ -585,7 +585,7 @@ dependencies { implementation 'commons-cli:commons-cli:1.9.0' implementation "org.bouncycastle:bcprov-jdk18on:${versions.bouncycastle}" implementation 'org.ldaptive:ldaptive:1.2.3' - implementation 'com.nimbusds:nimbus-jose-jwt:9.47' + implementation 'com.nimbusds:nimbus-jose-jwt:9.48' implementation 'com.rfksystems:blake2b:2.0.0' implementation 'com.password4j:password4j:1.8.2' From 1625da68b6c40e35ed6c6e88fba0b302150e925d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 09:01:22 -0500 Subject: [PATCH 03/21] Bump com.netflix.nebula.ospackage from 11.10.0 to 11.10.1 (#4982) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2c71116d09..03a8898352 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ plugins { id 'maven-publish' id 'com.diffplug.spotless' version '6.25.0' id 'checkstyle' - id 'com.netflix.nebula.ospackage' version "11.10.0" + id 'com.netflix.nebula.ospackage' version "11.10.1" id "org.gradle.test-retry" version "1.6.0" id 'eclipse' id "com.github.spotbugs" version "5.2.5" From 2fa7effd0dacc7cb76e0967579834865b4a09d63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:37:23 +0100 Subject: [PATCH 04/21] Bump ch.qos.logback:logback-classic from 1.5.12 to 1.5.15 (#4981) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 03a8898352..12ba2d94c2 100644 --- a/build.gradle +++ b/build.gradle @@ -497,7 +497,7 @@ configurations { force "org.apache.httpcomponents:httpcore:4.4.16" force "com.google.errorprone:error_prone_annotations:2.36.0" force "org.checkerframework:checker-qual:3.48.3" - force "ch.qos.logback:logback-classic:1.5.12" + force "ch.qos.logback:logback-classic:1.5.15" force "commons-io:commons-io:2.18.0" } } From fb4375d6671e6891618ffd73d0166dc4294d27a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:03:25 +0100 Subject: [PATCH 05/21] Bump org.apache.camel:camel-xmlsecurity from 3.22.2 to 3.22.3 (#4994) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 12ba2d94c2..896412b896 100644 --- a/build.gradle +++ b/build.gradle @@ -616,7 +616,7 @@ dependencies { runtimeOnly 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.2' runtimeOnly 'org.ow2.asm:asm:9.7.1' - testImplementation 'org.apache.camel:camel-xmlsecurity:3.22.2' + testImplementation 'org.apache.camel:camel-xmlsecurity:3.22.3' //OpenSAML implementation 'net.shibboleth.utilities:java-support:8.4.2' From a3345ef0e85ee6fb5b7744ac9b828cbf1e5c3a28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:03:45 +0100 Subject: [PATCH 06/21] Bump org.springframework.kafka:spring-kafka-test from 3.3.0 to 3.3.1 (#4993) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 896412b896..9eafa6f896 100644 --- a/build.gradle +++ b/build.gradle @@ -686,7 +686,7 @@ dependencies { testImplementation "org.apache.kafka:kafka_2.13:${kafka_version}:test" testImplementation "org.apache.kafka:kafka-clients:${kafka_version}:test" testImplementation 'commons-validator:commons-validator:1.9.0' - testImplementation 'org.springframework.kafka:spring-kafka-test:3.3.0' + testImplementation 'org.springframework.kafka:spring-kafka-test:3.3.1' testImplementation "org.springframework:spring-beans:${spring_version}" testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' From e83db44ed19c12d3f734970776b27d01a9243b4b Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Thu, 2 Jan 2025 15:25:34 +0100 Subject: [PATCH 07/21] Set default value for key/trust store type as constant for JDK PKCS setup (#5000) Signed-off-by: Andrey Pleskach --- .../security/ssl/config/SslCertificatesLoader.java | 6 +++--- .../opensearch/security/ssl/util/SSLConfigConstants.java | 2 ++ .../security/ssl/config/JdkSslCertificatesLoaderTest.java | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java b/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java index a3f0c39eed..a5eb7631f4 100644 --- a/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java +++ b/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java @@ -14,7 +14,6 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; -import java.security.KeyStore; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -27,6 +26,7 @@ import static org.opensearch.security.ssl.SecureSSLSettings.SECURE_SUFFIX; import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_PASSWORD; +import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE; import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_ALIAS; import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_FILEPATH; import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_KEY_PASSWORD; @@ -123,7 +123,7 @@ private KeyStoreConfiguration.JdkKeyStoreConfiguration buildJdkKeyStoreConfigura ) { return new KeyStoreConfiguration.JdkKeyStoreConfiguration( resolvePath(environment.settings().get(sslConfigSuffix + KEYSTORE_FILEPATH), environment), - environment.settings().get(sslConfigSuffix + KEYSTORE_TYPE, KeyStore.getDefaultType()), + environment.settings().get(sslConfigSuffix + KEYSTORE_TYPE, DEFAULT_STORE_TYPE), settings.get(KEYSTORE_ALIAS, null), keyStorePassword, keyPassword @@ -137,7 +137,7 @@ private TrustStoreConfiguration.JdkTrustStoreConfiguration buildJdkTrustStoreCon ) { return new TrustStoreConfiguration.JdkTrustStoreConfiguration( resolvePath(environment.settings().get(sslConfigSuffix + TRUSTSTORE_FILEPATH), environment), - environment.settings().get(sslConfigSuffix + TRUSTSTORE_TYPE, KeyStore.getDefaultType()), + environment.settings().get(sslConfigSuffix + TRUSTSTORE_TYPE, DEFAULT_STORE_TYPE), settings.get(TRUSTSTORE_ALIAS, null), trustStorePassword ); 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 0a67e1a520..ffe0a02ffd 100644 --- a/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java +++ b/src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java @@ -28,6 +28,8 @@ public final class SSLConfigConstants { + public static final String DEFAULT_STORE_TYPE = "JKS"; + public static final String SSL_PREFIX = "plugins.security.ssl."; public static final String HTTP_SETTINGS = "http"; diff --git a/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java b/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java index 829fc6a386..93df4ab7a2 100644 --- a/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java +++ b/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java @@ -30,6 +30,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_PASSWORD; +import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE; import static org.opensearch.security.ssl.util.SSLConfigConstants.ENABLED; import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_ALIAS; import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_FILEPATH; @@ -54,7 +55,7 @@ public class JdkSslCertificatesLoaderTest extends SslCertificatesLoaderTest { - static final Function resolveKeyStoreType = s -> isNull(s) ? KeyStore.getDefaultType() : s; + static final Function resolveKeyStoreType = s -> isNull(s) ? DEFAULT_STORE_TYPE : s; static final String SERVER_TRUSTSTORE_ALIAS = "server-truststore-alias"; From 4e10f0365a86151b7d8e792e71fd647bf457e05c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:34:23 +0100 Subject: [PATCH 08/21] Bump org.mockito:mockito-core from 5.14.2 to 5.15.2 (#5007) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9eafa6f896..5508a0d1ef 100644 --- a/build.gradle +++ b/build.gradle @@ -746,7 +746,7 @@ dependencies { integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14" integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16" integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5" - integrationTestImplementation "org.mockito:mockito-core:5.14.2" + integrationTestImplementation "org.mockito:mockito-core:5.15.2" //spotless implementation('com.google.googlejavaformat:google-java-format:1.25.2') { From 35aab3a1b00b1c628e1e016b17151c806b74d592 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:50:37 +0100 Subject: [PATCH 09/21] Bump org.apache.santuario:xmlsec from 2.3.4 to 2.3.5 (#5006) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5508a0d1ef..a7425c0df0 100644 --- a/build.gradle +++ b/build.gradle @@ -653,7 +653,7 @@ dependencies { runtimeOnly "org.glassfish.jaxb:txw2:${jaxb_version}" runtimeOnly 'com.fasterxml.woodstox:woodstox-core:6.7.0' runtimeOnly 'org.apache.ws.xmlschema:xmlschema-core:2.3.1' - runtimeOnly 'org.apache.santuario:xmlsec:2.3.4' + runtimeOnly 'org.apache.santuario:xmlsec:2.3.5' runtimeOnly "com.github.luben:zstd-jni:${versions.zstd}" runtimeOnly 'org.checkerframework:checker-qual:3.48.3' runtimeOnly "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" From 4c5a47b4111052c761b2be172b862afbf0b2a74f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:50:51 +0100 Subject: [PATCH 10/21] Bump ch.qos.logback:logback-classic from 1.5.15 to 1.5.16 (#5005) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a7425c0df0..f3ac3b980e 100644 --- a/build.gradle +++ b/build.gradle @@ -497,7 +497,7 @@ configurations { force "org.apache.httpcomponents:httpcore:4.4.16" force "com.google.errorprone:error_prone_annotations:2.36.0" force "org.checkerframework:checker-qual:3.48.3" - force "ch.qos.logback:logback-classic:1.5.15" + force "ch.qos.logback:logback-classic:1.5.16" force "commons-io:commons-io:2.18.0" } } From cd1dcbde45a6c2523505360f2fa15483b170e7f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:55:03 -0500 Subject: [PATCH 11/21] Bump org.checkerframework:checker-qual from 3.48.3 to 3.48.4 (#5004) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f3ac3b980e..cacfec77c5 100644 --- a/build.gradle +++ b/build.gradle @@ -496,7 +496,7 @@ configurations { force "org.apache.httpcomponents:httpclient:4.5.14" force "org.apache.httpcomponents:httpcore:4.4.16" force "com.google.errorprone:error_prone_annotations:2.36.0" - force "org.checkerframework:checker-qual:3.48.3" + force "org.checkerframework:checker-qual:3.48.4" force "ch.qos.logback:logback-classic:1.5.16" force "commons-io:commons-io:2.18.0" } @@ -655,7 +655,7 @@ dependencies { runtimeOnly 'org.apache.ws.xmlschema:xmlschema-core:2.3.1' runtimeOnly 'org.apache.santuario:xmlsec:2.3.5' runtimeOnly "com.github.luben:zstd-jni:${versions.zstd}" - runtimeOnly 'org.checkerframework:checker-qual:3.48.3' + runtimeOnly 'org.checkerframework:checker-qual:3.48.4' runtimeOnly "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" runtimeOnly 'org.scala-lang.modules:scala-java8-compat_3:1.0.2' From cfd8a497be60941681e5a27cbece4c8cb17627b5 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Fri, 10 Jan 2025 16:00:03 -0500 Subject: [PATCH 12/21] Update Gradle to 8.12 (#5017) Signed-off-by: Andriy Redko --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index eb1a55be0e..e1b837a19c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 4cd55e94dfd33e59f7f148dc6082a606fef51cdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 07:53:48 -0500 Subject: [PATCH 13/21] Bump commons-codec:commons-codec from 1.17.1 to 1.17.2 (#5019) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index cacfec77c5..e55e4df8cb 100644 --- a/build.gradle +++ b/build.gradle @@ -469,7 +469,7 @@ bundlePlugin { configurations { all { resolutionStrategy { - force 'commons-codec:commons-codec:1.17.1' + force 'commons-codec:commons-codec:1.17.2' force 'org.slf4j:slf4j-api:1.7.36' force 'org.scala-lang:scala-library:2.13.15' force "com.fasterxml.jackson:jackson-bom:${versions.jackson}" @@ -609,7 +609,7 @@ dependencies { 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.17.1' + runtimeOnly 'commons-codec:commons-codec:1.17.2' runtimeOnly 'org.cryptacular:cryptacular:1.2.7' compileOnly 'com.google.errorprone:error_prone_annotations:2.36.0' runtimeOnly 'com.sun.istack:istack-commons-runtime:4.2.0' From 16c93326253cb05bac8d66f8d1a054749253f167 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 07:55:06 -0500 Subject: [PATCH 14/21] Bump Wandalen/wretry.action from 3.7.3 to 3.8.0 (#5021) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f9e94ad6a..5a41062883 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: working-directory: downloaded-artifacts - name: Upload Coverage with retry - uses: Wandalen/wretry.action@v3.7.3 + uses: Wandalen/wretry.action@v3.8.0 with: attempt_limit: 5 attempt_delay: 2000 From 3e62dc9b181eb6f4aa948c2dc12c2f2dd05dbed9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:15:20 -0500 Subject: [PATCH 15/21] Bump org.scala-lang:scala-library from 2.13.15 to 2.13.16 (#5020) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e55e4df8cb..4f7d1614fb 100644 --- a/build.gradle +++ b/build.gradle @@ -471,7 +471,7 @@ configurations { resolutionStrategy { force 'commons-codec:commons-codec:1.17.2' force 'org.slf4j:slf4j-api:1.7.36' - force 'org.scala-lang:scala-library:2.13.15' + force 'org.scala-lang:scala-library:2.13.16' 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}" @@ -708,7 +708,7 @@ dependencies { testRuntimeOnly ("org.springframework:spring-core:${spring_version}") { exclude(group:'org.springframework', module: 'spring-jcl' ) } - testRuntimeOnly 'org.scala-lang:scala-library:2.13.15' + testRuntimeOnly 'org.scala-lang:scala-library:2.13.16' testRuntimeOnly 'com.typesafe.scala-logging:scala-logging_3:3.9.5' testRuntimeOnly('org.apache.zookeeper:zookeeper:3.9.3') { exclude(group:'ch.qos.logback', module: 'logback-classic' ) From 1924e410f80c4b958f6ec48c9691f8ae7a0af102 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 14 Jan 2025 17:18:46 -0500 Subject: [PATCH 16/21] Implement new extension points in IdentityPlugin and add ContextProvidingPluginSubject (#4896) Signed-off-by: Craig Perkins --- .../opensearch/security/ThreadPoolTests.java | 2 - .../http/ExampleSystemIndexPlugin.java | 27 ----- .../privileges/ActionPrivilegesTest.java | 40 ++++++- .../{ => systemindex}/SystemIndexTests.java | 105 ++++++++++++++++- .../IndexDocumentIntoSystemIndexAction.java | 22 ++++ .../IndexDocumentIntoSystemIndexRequest.java | 48 ++++++++ .../IndexDocumentIntoSystemIndexResponse.java | 48 ++++++++ .../sampleplugin/PluginContextSwitcher.java | 35 ++++++ ...dexDocumentIntoMixOfSystemIndexAction.java | 76 ++++++++++++ ...ulkIndexDocumentIntoSystemIndexAction.java | 79 +++++++++++++ ...estIndexDocumentIntoSystemIndexAction.java | 49 ++++++++ .../RestRunClusterHealthAction.java | 50 ++++++++ .../sampleplugin/RunClusterHealthAction.java | 22 ++++ .../sampleplugin/RunClusterHealthRequest.java | 40 +++++++ .../RunClusterHealthResponse.java | 42 +++++++ .../sampleplugin/SystemIndexPlugin1.java | 109 ++++++++++++++++++ .../sampleplugin/SystemIndexPlugin2.java | 61 ++++++++++ ...ortIndexDocumentIntoSystemIndexAction.java | 96 +++++++++++++++ .../TransportRunClusterHealthAction.java | 83 +++++++++++++ .../test/framework/cluster/LocalCluster.java | 9 +- .../test/framework/matcher/RestMatchers.java | 68 +++++++++++ .../security/OpenSearchSecurityPlugin.java | 15 ++- .../security/auth/BackendRegistry.java | 22 ++-- .../security/auth/UserSubjectImpl.java | 51 ++++++++ .../security/filter/SecurityFilter.java | 12 +- .../ContextProvidingPluginSubject.java | 51 ++++++++ .../security/privileges/ActionPrivileges.java | 60 +++++++++- .../privileges/PrivilegesEvaluator.java | 12 +- .../SystemIndexAccessEvaluator.java | 27 +++++ .../security/support/ConfigConstants.java | 3 + .../org/opensearch/security/user/User.java | 11 +- .../security/auth/UserSubjectImplTests.java | 54 +++++++++ .../ContextProvidingPluginSubjectTests.java | 59 ++++++++++ 33 files changed, 1424 insertions(+), 64 deletions(-) delete mode 100644 src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java rename src/integrationTest/java/org/opensearch/security/{ => systemindex}/SystemIndexTests.java (51%) create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexRequest.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestIndexDocumentIntoSystemIndexAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthRequest.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin2.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java create mode 100644 src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.java create mode 100644 src/main/java/org/opensearch/security/auth/UserSubjectImpl.java create mode 100644 src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java create mode 100644 src/test/java/org/opensearch/security/auth/UserSubjectImplTests.java create mode 100644 src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java diff --git a/src/integrationTest/java/org/opensearch/security/ThreadPoolTests.java b/src/integrationTest/java/org/opensearch/security/ThreadPoolTests.java index 6fb1a2c91b..10aaab0f73 100644 --- a/src/integrationTest/java/org/opensearch/security/ThreadPoolTests.java +++ b/src/integrationTest/java/org/opensearch/security/ThreadPoolTests.java @@ -21,7 +21,6 @@ import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.security.http.ExampleSystemIndexPlugin; import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; @@ -47,7 +46,6 @@ public class ThreadPoolTests { .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) - .plugin(ExampleSystemIndexPlugin.class) .nodeSettings(Map.of(SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + USER_ADMIN.getName() + "__" + ALL_ACCESS.getName()))) .build(); diff --git a/src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java b/src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java deleted file mode 100644 index b4877aae14..0000000000 --- a/src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.Collection; -import java.util.Collections; - -import org.opensearch.common.settings.Settings; -import org.opensearch.indices.SystemIndexDescriptor; -import org.opensearch.plugins.Plugin; -import org.opensearch.plugins.SystemIndexPlugin; - -public class ExampleSystemIndexPlugin extends Plugin implements SystemIndexPlugin { - - @Override - public Collection getSystemIndexDescriptors(Settings settings) { - final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(".system-index1", "System index 1"); - return Collections.singletonList(systemIndexDescriptor); - } -} diff --git a/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java b/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java index 7807dae748..1f60cf92d5 100644 --- a/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java +++ b/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java @@ -126,6 +126,28 @@ public void wildcard() throws Exception { ); } + @Test + public void wildcardByUsername() throws Exception { + SecurityDynamicConfiguration roles = SecurityDynamicConfiguration.empty(CType.ROLES); + + ActionPrivileges subject = new ActionPrivileges( + roles, + FlattenedActionGroups.EMPTY, + null, + Settings.EMPTY, + Map.of("plugin:org.opensearch.sample.SamplePlugin", Set.of("*")) + ); + + assertThat( + subject.hasClusterPrivilege(ctxByUsername("plugin:org.opensearch.sample.SamplePlugin"), "cluster:whatever"), + isAllowed() + ); + assertThat( + subject.hasClusterPrivilege(ctx("plugin:org.opensearch.other.OtherPlugin"), "cluster:whatever"), + isForbidden(missingPrivileges("cluster:whatever")) + ); + } + @Test public void explicit_wellKnown() throws Exception { SecurityDynamicConfiguration roles = SecurityDynamicConfiguration.fromYaml("non_explicit_role:\n" + // @@ -455,7 +477,8 @@ public IndicesAndAliases(IndexSpec indexSpec, ActionSpec actionSpec, Statefulnes settings, WellKnownActions.CLUSTER_ACTIONS, WellKnownActions.INDEX_ACTIONS, - WellKnownActions.INDEX_ACTIONS + WellKnownActions.INDEX_ACTIONS, + Map.of() ); if (statefulness == Statefulness.STATEFUL || statefulness == Statefulness.STATEFUL_LIMITED) { @@ -1030,4 +1053,19 @@ static PrivilegesEvaluationContext ctx(String... roles) { null ); } + + static PrivilegesEvaluationContext ctxByUsername(String username) { + User user = new User(username); + user.addAttributes(ImmutableMap.of("attrs.dept_no", "a11")); + return new PrivilegesEvaluationContext( + user, + ImmutableSet.of(), + null, + null, + null, + null, + new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)), + null + ); + } } diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/systemindex/SystemIndexTests.java similarity index 51% rename from src/integrationTest/java/org/opensearch/security/SystemIndexTests.java rename to src/integrationTest/java/org/opensearch/security/systemindex/SystemIndexTests.java index ae068255da..3435e6dbdc 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/SystemIndexTests.java @@ -7,7 +7,7 @@ * compatible open source license. * */ -package org.opensearch.security; +package org.opensearch.security.systemindex; import java.util.List; import java.util.Map; @@ -19,17 +19,22 @@ import org.junit.runner.RunWith; import org.opensearch.core.rest.RestStatus; -import org.opensearch.security.http.ExampleSystemIndexPlugin; +import org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1; +import org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin2; import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; 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.matcher.RestMatchers; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED; import static org.opensearch.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY; +import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1.SYSTEM_INDEX_1; +import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin2.SYSTEM_INDEX_2; import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN; @@ -44,7 +49,7 @@ public class SystemIndexTests { .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) - .plugin(ExampleSystemIndexPlugin.class) + .plugin(SystemIndexPlugin1.class, SystemIndexPlugin2.class) .nodeSettings( Map.of( SECURITY_RESTAPI_ROLES_ENABLED, @@ -89,6 +94,100 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() { } } + @Test + public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_1); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + assertThat(response.getBody(), containsString(SystemIndexPlugin1.class.getCanonicalName())); + } + } + + @Test + public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByOtherPlugin() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_2); + + assertThat( + response, + RestMatchers.isForbidden( + "/error/root_cause/0/reason", + "no permissions for [] and User [name=plugin:org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1" + ) + ); + } + } + + @Test + public void testPluginShouldBeAbleToCreateSystemIndexButUserShouldNotBeAbleToIndex() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_1 + "?runAs=user"); + + assertThat(response, RestMatchers.isForbidden("/error/root_cause/0/reason", "no permissions for [] and User [name=admin")); + } + } + + @Test + public void testPluginShouldNotBeAbleToRunClusterActions() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.get("try-cluster-health/plugin"); + + assertThat( + response, + RestMatchers.isForbidden( + "/error/root_cause/0/reason", + "no permissions for [cluster:monitor/health] and User [name=plugin:org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1" + ) + ); + } + } + + @Test + public void testAdminUserShouldBeAbleToRunClusterActions() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.get("try-cluster-health/user"); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + } + } + + @Test + public void testAuthenticatedUserShouldBeAbleToRunClusterActions() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.get("try-cluster-health/default"); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + } + } + + @Test + public void testPluginShouldBeAbleToBulkIndexDocumentIntoItsSystemIndex() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-bulk-index/" + SYSTEM_INDEX_1); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + } + } + + @Test + public void testPluginShouldNotBeAbleToBulkIndexDocumentIntoMixOfSystemIndexWhereAtLeastOneDoesNotBelongToPlugin() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + client.put(".system-index1"); + client.put(".system-index2"); + } + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-bulk-mixed-index"); + + assertThat( + response.getBody(), + containsString( + "no permissions for [] and User [name=plugin:org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1" + ) + ); + } + } + @Test public void regularUserShouldGetNoResultsWhenSearchingSystemIndex() { // Create system index and index a dummy document as the super admin user, data returned to super admin diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..a78c57e1d1 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,22 @@ +/* + * 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.systemindex.sampleplugin; + +import org.opensearch.action.ActionType; + +public class IndexDocumentIntoSystemIndexAction extends ActionType { + public static final IndexDocumentIntoSystemIndexAction INSTANCE = new IndexDocumentIntoSystemIndexAction(); + public static final String NAME = "cluster:mock/systemindex/index"; + + private IndexDocumentIntoSystemIndexAction() { + super(NAME, IndexDocumentIntoSystemIndexResponse::new); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexRequest.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexRequest.java new file mode 100644 index 0000000000..ae7c345457 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexRequest.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.security.systemindex.sampleplugin; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; + +public class IndexDocumentIntoSystemIndexRequest extends ActionRequest { + + private final String indexName; + + private final String runAs; + + public IndexDocumentIntoSystemIndexRequest(String indexName, String runAs) { + this.indexName = indexName; + this.runAs = runAs; + } + + public IndexDocumentIntoSystemIndexRequest(StreamInput in) throws IOException { + super(in); + this.indexName = in.readString(); + this.runAs = in.readOptionalString(); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getIndexName() { + return this.indexName; + } + + public String getRunAs() { + return this.runAs; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.java new file mode 100644 index 0000000000..e154742fe2 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.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.security.systemindex.sampleplugin; + +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here +import java.io.IOException; + +import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +// CS-ENFORCE-SINGLE + +public class IndexDocumentIntoSystemIndexResponse extends AcknowledgedResponse implements ToXContentObject { + + private String plugin; + + public IndexDocumentIntoSystemIndexResponse(boolean status, String plugin) { + super(status); + this.plugin = plugin; + } + + public IndexDocumentIntoSystemIndexResponse(StreamInput in) throws IOException { + super(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(plugin); + } + + @Override + public void addCustomFields(XContentBuilder builder, ToXContent.Params params) throws IOException { + super.addCustomFields(builder, params); + builder.field("plugin", plugin); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.java new file mode 100644 index 0000000000..4ef420efdb --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.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.systemindex.sampleplugin; + +import java.util.Objects; +import java.util.concurrent.Callable; + +import org.opensearch.identity.PluginSubject; + +public class PluginContextSwitcher { + private PluginSubject pluginSubject; + + public PluginContextSwitcher() {} + + public void initialize(PluginSubject pluginSubject) { + Objects.requireNonNull(pluginSubject); + this.pluginSubject = pluginSubject; + } + + public T runAs(Callable callable) { + try { + return pluginSubject.runAs(callable); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java new file mode 100644 index 0000000000..e9b569a263 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -0,0 +1,76 @@ +/* + * 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.systemindex.sampleplugin; + +import java.util.List; + +import org.opensearch.action.bulk.BulkRequest; +import org.opensearch.action.bulk.BulkRequestBuilder; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.PUT; +import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1.SYSTEM_INDEX_1; +import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin2.SYSTEM_INDEX_2; + +public class RestBulkIndexDocumentIntoMixOfSystemIndexAction extends BaseRestHandler { + + private final Client client; + private final PluginContextSwitcher contextSwitcher; + + public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + this.client = client; + this.contextSwitcher = contextSwitcher; + } + + @Override + public List routes() { + return singletonList(new Route(PUT, "/try-create-and-bulk-mixed-index")); + } + + @Override + public String getName() { + return "test_bulk_index_document_into_mix_of_system_index_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + return new RestChannelConsumer() { + + @Override + public void accept(RestChannel channel) throws Exception { + contextSwitcher.runAs(() -> { + BulkRequestBuilder builder = client.prepareBulk(); + builder.add(new IndexRequest(SYSTEM_INDEX_1).source("content", 1)); + builder.add(new IndexRequest(SYSTEM_INDEX_2).source("content", 1)); + builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkRequest bulkRequest = builder.request(); + client.bulk(bulkRequest, ActionListener.wrap(r -> { + channel.sendResponse( + new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) + ); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); + return null; + }); + } + }; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..8b37e54164 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.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.security.systemindex.sampleplugin; + +import java.util.List; + +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.bulk.BulkRequest; +import org.opensearch.action.bulk.BulkRequestBuilder; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.PUT; + +public class RestBulkIndexDocumentIntoSystemIndexAction extends BaseRestHandler { + + private final Client client; + private final PluginContextSwitcher contextSwitcher; + + public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + this.client = client; + this.contextSwitcher = contextSwitcher; + } + + @Override + public List routes() { + return singletonList(new Route(PUT, "/try-create-and-bulk-index/{index}")); + } + + @Override + public String getName() { + return "test_bulk_index_document_into_system_index_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String indexName = request.param("index"); + return new RestChannelConsumer() { + + @Override + public void accept(RestChannel channel) throws Exception { + contextSwitcher.runAs(() -> { + client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { + BulkRequestBuilder builder = client.prepareBulk(); + builder.add(new IndexRequest(indexName).source("{\"content\":1}", XContentType.JSON)); + builder.add(new IndexRequest(indexName).source("{\"content\":2}", XContentType.JSON)); + builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkRequest bulkRequest = builder.request(); + client.bulk(bulkRequest, ActionListener.wrap(r2 -> { + channel.sendResponse( + new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) + ); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); + return null; + }); + } + }; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestIndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..9092c97988 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestIndexDocumentIntoSystemIndexAction.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.security.systemindex.sampleplugin; + +import java.util.List; + +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.PUT; + +public class RestIndexDocumentIntoSystemIndexAction extends BaseRestHandler { + + private final Client client; + + public RestIndexDocumentIntoSystemIndexAction(Client client) { + this.client = client; + } + + @Override + public List routes() { + return singletonList(new Route(PUT, "/try-create-and-index/{index}")); + } + + @Override + public String getName() { + return "test_index_document_into_system_index_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String runAs = request.param("runAs"); + String indexName = request.param("index"); + IndexDocumentIntoSystemIndexRequest indexRequest = new IndexDocumentIntoSystemIndexRequest(indexName, runAs); + return channel -> client.execute(IndexDocumentIntoSystemIndexAction.INSTANCE, indexRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.java new file mode 100644 index 0000000000..f4674b4377 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.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.security.systemindex.sampleplugin; + +import java.util.List; + +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class RestRunClusterHealthAction extends BaseRestHandler { + + private final Client client; + private final PluginContextSwitcher contextSwitcher; + + public RestRunClusterHealthAction(Client client, PluginContextSwitcher contextSwitcher) { + this.client = client; + this.contextSwitcher = contextSwitcher; + } + + @Override + public List routes() { + return singletonList(new Route(GET, "/try-cluster-health/{runAs}")); + } + + @Override + public String getName() { + return "test_run_cluster_health_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String runAs = request.param("runAs"); + RunClusterHealthRequest runRequest = new RunClusterHealthRequest(runAs); + return channel -> client.execute(RunClusterHealthAction.INSTANCE, runRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java new file mode 100644 index 0000000000..dca6b8d2b7 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java @@ -0,0 +1,22 @@ +/* + * 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.systemindex.sampleplugin; + +import org.opensearch.action.ActionType; + +public class RunClusterHealthAction extends ActionType { + public static final RunClusterHealthAction INSTANCE = new RunClusterHealthAction(); + public static final String NAME = "cluster:mock/monitor/health"; + + private RunClusterHealthAction() { + super(NAME, RunClusterHealthResponse::new); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthRequest.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthRequest.java new file mode 100644 index 0000000000..099264313e --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthRequest.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.security.systemindex.sampleplugin; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; + +public class RunClusterHealthRequest extends ActionRequest { + + private final String runAs; + + public RunClusterHealthRequest(String runAs) { + this.runAs = runAs; + } + + public RunClusterHealthRequest(StreamInput in) throws IOException { + super(in); + this.runAs = in.readString(); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getRunAs() { + return this.runAs; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java new file mode 100644 index 0000000000..a500755e22 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java @@ -0,0 +1,42 @@ +/* + * 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.systemindex.sampleplugin; + +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here +import java.io.IOException; + +import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +// CS-ENFORCE-SINGLE + +public class RunClusterHealthResponse extends AcknowledgedResponse implements ToXContentObject { + + public RunClusterHealthResponse(boolean status) { + super(status); + } + + public RunClusterHealthResponse(StreamInput in) throws IOException { + super(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + } + + @Override + public void addCustomFields(XContentBuilder builder, Params params) throws IOException { + super.addCustomFields(builder, params); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.java new file mode 100644 index 0000000000..9805619965 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.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.systemindex.sampleplugin; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.opensearch.action.ActionRequest; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.identity.PluginSubject; +import org.opensearch.indices.SystemIndexDescriptor; +import org.opensearch.plugins.IdentityAwarePlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +public class SystemIndexPlugin1 extends Plugin implements SystemIndexPlugin, IdentityAwarePlugin { + public static final String SYSTEM_INDEX_1 = ".system-index1"; + + private PluginContextSwitcher contextSwitcher; + + private Client client; + + @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 + ) { + this.client = client; + this.contextSwitcher = new PluginContextSwitcher(); + return List.of(contextSwitcher); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(SYSTEM_INDEX_1, "System index 1"); + return Collections.singletonList(systemIndexDescriptor); + } + + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + return List.of( + new RestIndexDocumentIntoSystemIndexAction(client), + new RestRunClusterHealthAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoSystemIndexAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, contextSwitcher) + ); + } + + @Override + public List> getActions() { + return Arrays.asList( + new ActionHandler<>(IndexDocumentIntoSystemIndexAction.INSTANCE, TransportIndexDocumentIntoSystemIndexAction.class), + new ActionHandler<>(RunClusterHealthAction.INSTANCE, TransportRunClusterHealthAction.class) + ); + } + + @Override + public void assignSubject(PluginSubject pluginSystemSubject) { + if (contextSwitcher != null) { + this.contextSwitcher.initialize(pluginSystemSubject); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin2.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin2.java new file mode 100644 index 0000000000..f91cbd3226 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin2.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.systemindex.sampleplugin; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Supplier; + +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.indices.SystemIndexDescriptor; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +public class SystemIndexPlugin2 extends Plugin implements SystemIndexPlugin { + public static final String SYSTEM_INDEX_2 = ".system-index2"; + + private Client client; + + @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 + ) { + this.client = client; + return Collections.emptyList(); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(SYSTEM_INDEX_2, "System index 2"); + return Collections.singletonList(systemIndexDescriptor); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..6138cfbb54 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,96 @@ +/* + * 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.systemindex.sampleplugin; + +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.tasks.Task; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +public class TransportIndexDocumentIntoSystemIndexAction extends HandledTransportAction< + IndexDocumentIntoSystemIndexRequest, + IndexDocumentIntoSystemIndexResponse> { + + private final Client client; + private final ThreadPool threadPool; + private final PluginContextSwitcher contextSwitcher; + private final IdentityService identityService; + + @Inject + public TransportIndexDocumentIntoSystemIndexAction( + final TransportService transportService, + final ActionFilters actionFilters, + final Client client, + final ThreadPool threadPool, + final PluginContextSwitcher contextSwitcher, + final IdentityService identityService + ) { + super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); + this.client = client; + this.threadPool = threadPool; + this.contextSwitcher = contextSwitcher; + this.identityService = identityService; + } + + @Override + protected void doExecute( + Task task, + IndexDocumentIntoSystemIndexRequest request, + ActionListener actionListener + ) { + String indexName = request.getIndexName(); + String runAs = request.getRunAs(); + Subject userSubject = identityService.getCurrentSubject(); + try { + contextSwitcher.runAs(() -> { + client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { + if ("user".equalsIgnoreCase(runAs)) { + userSubject.runAs(() -> { + client.index( + new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON), + ActionListener.wrap(r2 -> { + User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); + }, actionListener::onFailure) + ); + return null; + }); + } else { + client.index( + new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON), + ActionListener.wrap(r2 -> { + User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); + }, actionListener::onFailure) + ); + } + }, actionListener::onFailure)); + return null; + }); + } catch (Exception ex) { + throw new RuntimeException("Unexpected error: " + ex.getMessage()); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java new file mode 100644 index 0000000000..310262c947 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java @@ -0,0 +1,83 @@ +/* + * 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.systemindex.sampleplugin; + +import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; +import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; +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.core.action.ActionListener; +import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; +import org.opensearch.tasks.Task; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +public class TransportRunClusterHealthAction extends HandledTransportAction { + + private final Client client; + private final ThreadPool threadPool; + private final PluginContextSwitcher contextSwitcher; + private final IdentityService identityService; + + @Inject + public TransportRunClusterHealthAction( + final TransportService transportService, + final ActionFilters actionFilters, + final Client client, + final ThreadPool threadPool, + final PluginContextSwitcher contextSwitcher, + final IdentityService identityService + ) { + super(RunClusterHealthAction.NAME, transportService, actionFilters, RunClusterHealthRequest::new); + this.client = client; + this.threadPool = threadPool; + this.contextSwitcher = contextSwitcher; + this.identityService = identityService; + } + + @Override + protected void doExecute(Task task, RunClusterHealthRequest request, ActionListener actionListener) { + String runAs = request.getRunAs(); + if ("user".equalsIgnoreCase(runAs)) { + Subject user = identityService.getCurrentSubject(); + try { + user.runAs(() -> { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + actionListener::onFailure + ); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else if ("plugin".equalsIgnoreCase(runAs)) { + contextSwitcher.runAs(() -> { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + actionListener::onFailure + ); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } else { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + actionListener::onFailure + ); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + } + } +} 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 5ae8c0b125..30e826829a 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -414,10 +414,11 @@ public Builder nodeSpecificSettings(int nodeNumber, Map settings } /** - * Adds additional plugins to the cluster - */ - public Builder plugin(Class plugin) { - this.plugins.add(plugin); + * Adds additional plugins to the cluster + */ + @SafeVarargs + public final Builder plugin(Class... plugins) { + this.plugins.addAll(List.of(plugins)); return this; } diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.java new file mode 100644 index 0000000000..b885074688 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.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.DiagnosingMatcher; + +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +public class RestMatchers { + + private RestMatchers() {} + + public static DiagnosingMatcher isForbidden(String jsonPointer, String patternString) { + return new DiagnosingMatcher() { + + @Override + public void describeTo(Description description) { + description.appendText("Response has status 403 Forbidden with a JSON response that has the value ") + .appendValue(patternString) + .appendText(" at ") + .appendValue(jsonPointer); + } + + @Override + protected boolean matches(Object item, Description mismatchDescription) { + if (!(item instanceof HttpResponse)) { + mismatchDescription.appendValue(item).appendText(" is not a HttpResponse"); + return false; + } + + HttpResponse response = (HttpResponse) item; + + if (response.getStatusCode() != 403) { + mismatchDescription.appendText("Status is not 403 Forbidden: ").appendText("\n").appendValue(item); + return false; + } + + try { + String value = response.getTextFromJsonBody(jsonPointer); + + if (value == null) { + mismatchDescription.appendText("Could not find value at " + jsonPointer).appendText("\n").appendValue(item); + return false; + } + + if (value.contains(patternString)) { + return true; + } else { + mismatchDescription.appendText("Value at " + jsonPointer + " does not match pattern: " + patternString + "\n") + .appendValue(item); + return false; + } + } catch (Exception e) { + mismatchDescription.appendText("Parsing request body failed with " + e).appendText("\n").appendValue(item); + return false; + } + } + }; + } +} diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 25ab6c0c06..13d0f79330 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -43,6 +43,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -69,6 +70,7 @@ import org.opensearch.SpecialPermission; import org.opensearch.Version; import org.opensearch.action.ActionRequest; +import org.opensearch.action.bulk.BulkAction; import org.opensearch.action.search.PitService; import org.opensearch.action.search.SearchScrollAction; import org.opensearch.action.support.ActionFilter; @@ -110,7 +112,6 @@ import org.opensearch.http.netty4.ssl.SecureNetty4HttpServerTransport; import org.opensearch.identity.PluginSubject; import org.opensearch.identity.Subject; -import org.opensearch.identity.noop.NoopSubject; import org.opensearch.index.IndexModule; import org.opensearch.index.cache.query.QueryCache; import org.opensearch.indices.IndicesService; @@ -164,7 +165,7 @@ import org.opensearch.security.hasher.PasswordHasherFactory; import org.opensearch.security.http.NonSslHttpServerTransport; import org.opensearch.security.http.XFFResolver; -import org.opensearch.security.identity.NoopPluginSubject; +import org.opensearch.security.identity.ContextProvidingPluginSubject; import org.opensearch.security.identity.SecurityTokenManager; import org.opensearch.security.privileges.ActionPrivileges; import org.opensearch.security.privileges.PrivilegesEvaluationException; @@ -221,6 +222,7 @@ import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.SECURITY_CONFIG_UPDATE; import static org.opensearch.security.setting.DeprecatedSettings.checkForDeprecatedSetting; +import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER; import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX; import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE; import static org.opensearch.security.support.ConfigConstants.SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED; @@ -2135,8 +2137,7 @@ public Collection getSystemIndexDescriptors(Settings sett @Override public Subject getCurrentSubject() { - // Not supported - return new NoopSubject(); + return (Subject) threadPool.getThreadContext().getPersistent(OPENDISTRO_SECURITY_AUTHENTICATED_USER); } @Override @@ -2146,7 +2147,11 @@ public SecurityTokenManager getTokenManager() { @Override public PluginSubject getPluginSubject(Plugin plugin) { - return new NoopPluginSubject(threadPool); + Set clusterActions = new HashSet<>(); + clusterActions.add(BulkAction.NAME); + PluginSubject subject = new ContextProvidingPluginSubject(threadPool, settings, plugin); + sf.updatePluginToClusterActions(subject.getPrincipal().getName(), clusterActions); + return subject; } @Override diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 0b00bcf943..b527b8fca2 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -55,6 +55,7 @@ import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.core.rest.RestStatus; +import org.opensearch.identity.UserSubject; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.auth.blocking.ClientBlockRegistry; import org.opensearch.security.auth.internal.NoOpAuthenticationBackend; @@ -223,7 +224,10 @@ public boolean authenticate(final SecurityRequestChannel request) { if (adminDns.isAdminDN(sslPrincipal)) { // PKI authenticated REST call - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, new User(sslPrincipal)); + User superuser = new User(sslPrincipal); + UserSubject subject = new UserSubjectImpl(threadPool, superuser); + threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, superuser); auditLog.logSucceededLogin(sslPrincipal, true, null, request); return true; } @@ -387,14 +391,11 @@ public boolean authenticate(final SecurityRequestChannel request) { if (authenticated) { final User impersonatedUser = impersonate(request, authenticatedUser); - threadPool.getThreadContext() - .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, impersonatedUser == null ? authenticatedUser : impersonatedUser); - auditLog.logSucceededLogin( - (impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), - false, - authenticatedUser.getName(), - request - ); + final User effectiveUser = impersonatedUser == null ? authenticatedUser : impersonatedUser; + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, effectiveUser); + UserSubject subject = new UserSubjectImpl(threadPool, effectiveUser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); + auditLog.logSucceededLogin(effectiveUser.getName(), false, authenticatedUser.getName(), request); } else { if (isDebugEnabled) { log.debug("User still not authenticated after checking {} auth domains", restAuthDomains.size()); @@ -421,7 +422,10 @@ public boolean authenticate(final SecurityRequestChannel request) { User anonymousUser = new User(User.ANONYMOUS.getName(), new HashSet(User.ANONYMOUS.getRoles()), null); anonymousUser.setRequestedTenant(tenant); + UserSubject subject = new UserSubjectImpl(threadPool, anonymousUser); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); auditLog.logSucceededLogin(anonymousUser.getName(), false, null, request); if (isDebugEnabled) { log.debug("Anonymous User is authenticated"); diff --git a/src/main/java/org/opensearch/security/auth/UserSubjectImpl.java b/src/main/java/org/opensearch/security/auth/UserSubjectImpl.java new file mode 100644 index 0000000000..63adc559e3 --- /dev/null +++ b/src/main/java/org/opensearch/security/auth/UserSubjectImpl.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.auth; + +import java.security.Principal; +import java.util.concurrent.Callable; + +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.identity.NamedPrincipal; +import org.opensearch.identity.UserSubject; +import org.opensearch.identity.tokens.AuthToken; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.ThreadPool; + +public class UserSubjectImpl implements UserSubject { + private final NamedPrincipal userPrincipal; + private final ThreadPool threadPool; + private final User user; + + UserSubjectImpl(ThreadPool threadPool, User user) { + this.threadPool = threadPool; + this.user = user; + this.userPrincipal = new NamedPrincipal(user.getName()); + } + + @Override + public void authenticate(AuthToken authToken) { + // not implemented + } + + @Override + public Principal getPrincipal() { + return userPrincipal; + } + + @Override + public T runAs(Callable callable) throws Exception { + try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + return callable.call(); + } + } +} diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index 3323c9e38a..6b23fb6b53 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -193,15 +193,9 @@ private void ap attachSourceFieldContext(request); } 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) { 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. - // However, if another plugin injected a user in the ThreadContext, we still need - // to perform privileges checks. - enforcePrivilegesEvaluation = true; } final boolean userIsAdmin = isUserAdmin(user, adminDns); final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadContext); @@ -320,7 +314,7 @@ private void ap if (Origin.LOCAL.toString().equals(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) && (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) && (injectedRoles == null) - && !enforcePrivilegesEvaluation) { + && (user == null)) { chain.proceed(task, action, request, listener); return; @@ -528,6 +522,10 @@ private boolean checkImmutableIndices(Object request, ActionListener listener) { return false; } + public void updatePluginToClusterActions(String pluginIdentifier, Set clusterActions) { + evalp.updatePluginToClusterActions(pluginIdentifier, clusterActions); + } + private boolean isRequestIndexImmutable(Object request) { final IndexResolverReplacer.Resolved resolved = indexResolverReplacer.resolveRequest(request); if (resolved.isLocalAll()) { diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java new file mode 100644 index 0000000000..ab6dddceba --- /dev/null +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.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.identity; + +import java.security.Principal; +import java.util.concurrent.Callable; + +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.identity.NamedPrincipal; +import org.opensearch.identity.PluginSubject; +import org.opensearch.plugins.Plugin; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.ThreadPool; + +public class ContextProvidingPluginSubject implements PluginSubject { + private final ThreadPool threadPool; + private final NamedPrincipal pluginPrincipal; + private final User pluginUser; + + public ContextProvidingPluginSubject(ThreadPool threadPool, Settings settings, Plugin plugin) { + super(); + this.threadPool = threadPool; + String principal = "plugin:" + plugin.getClass().getCanonicalName(); + this.pluginPrincipal = new NamedPrincipal(principal); + // Convention for plugin username. Prefixed with 'plugin:'. ':' is forbidden from usernames, so this + // guarantees that a user with this username cannot be created by other means. + this.pluginUser = new User(principal); + } + + @Override + public Principal getPrincipal() { + return pluginPrincipal; + } + + @Override + public T runAs(Callable callable) throws Exception { + try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, pluginUser); + return callable.call(); + } + } +} diff --git a/src/main/java/org/opensearch/security/privileges/ActionPrivileges.java b/src/main/java/org/opensearch/security/privileges/ActionPrivileges.java index 87ac32d090..eb560ed901 100644 --- a/src/main/java/org/opensearch/security/privileges/ActionPrivileges.java +++ b/src/main/java/org/opensearch/security/privileges/ActionPrivileges.java @@ -90,9 +90,10 @@ public ActionPrivileges( Settings settings, ImmutableSet wellKnownClusterActions, ImmutableSet wellKnownIndexActions, - ImmutableSet explicitlyRequiredIndexActions + ImmutableSet explicitlyRequiredIndexActions, + Map> pluginToClusterActions ) { - this.cluster = new ClusterPrivileges(roles, actionGroups, wellKnownClusterActions); + this.cluster = new ClusterPrivileges(roles, actionGroups, wellKnownClusterActions, pluginToClusterActions); this.index = new IndexPrivileges(roles, actionGroups, wellKnownIndexActions, explicitlyRequiredIndexActions); this.roles = roles; this.actionGroups = actionGroups; @@ -115,7 +116,27 @@ public ActionPrivileges( settings, WellKnownActions.CLUSTER_ACTIONS, WellKnownActions.INDEX_ACTIONS, - WellKnownActions.EXPLICITLY_REQUIRED_INDEX_ACTIONS + WellKnownActions.EXPLICITLY_REQUIRED_INDEX_ACTIONS, + Map.of() + ); + } + + public ActionPrivileges( + SecurityDynamicConfiguration roles, + FlattenedActionGroups actionGroups, + Supplier> indexMetadataSupplier, + Settings settings, + Map> pluginToClusterActions + ) { + this( + roles, + actionGroups, + indexMetadataSupplier, + settings, + WellKnownActions.CLUSTER_ACTIONS, + WellKnownActions.INDEX_ACTIONS, + WellKnownActions.EXPLICITLY_REQUIRED_INDEX_ACTIONS, + pluginToClusterActions ); } @@ -297,6 +318,8 @@ static class ClusterPrivileges { */ private final ImmutableMap rolesToActionMatcher; + private final ImmutableMap usersToActionMatcher; + private final ImmutableSet wellKnownClusterActions; /** @@ -310,7 +333,8 @@ static class ClusterPrivileges { ClusterPrivileges( SecurityDynamicConfiguration roles, FlattenedActionGroups actionGroups, - ImmutableSet wellKnownClusterActions + ImmutableSet wellKnownClusterActions, + Map> pluginToClusterActions ) { DeduplicatingCompactSubSetBuilder roleSetBuilder = new DeduplicatingCompactSubSetBuilder<>( roles.getCEntries().keySet() @@ -318,6 +342,7 @@ static class ClusterPrivileges { Map> actionToRoles = new HashMap<>(); ImmutableSet.Builder rolesWithWildcardPermissions = ImmutableSet.builder(); ImmutableMap.Builder rolesToActionMatcher = ImmutableMap.builder(); + ImmutableMap.Builder usersToActionMatcher = ImmutableMap.builder(); for (Map.Entry entry : roles.getCEntries().entrySet()) { try { @@ -367,6 +392,14 @@ static class ClusterPrivileges { } } + if (pluginToClusterActions != null) { + for (String pluginIdentifier : pluginToClusterActions.keySet()) { + Set clusterActions = pluginToClusterActions.get(pluginIdentifier); + WildcardMatcher matcher = WildcardMatcher.from(clusterActions); + usersToActionMatcher.put(pluginIdentifier, matcher); + } + } + DeduplicatingCompactSubSetBuilder.Completed completedRoleSetBuilder = roleSetBuilder.build(); this.actionToRoles = actionToRoles.entrySet() @@ -374,6 +407,7 @@ static class ClusterPrivileges { .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().build(completedRoleSetBuilder))); this.rolesWithWildcardPermissions = rolesWithWildcardPermissions.build(); this.rolesToActionMatcher = rolesToActionMatcher.build(); + this.usersToActionMatcher = usersToActionMatcher.build(); this.wellKnownClusterActions = wellKnownClusterActions; } @@ -407,6 +441,14 @@ PrivilegesEvaluatorResponse providesPrivilege(PrivilegesEvaluationContext contex } } + // 4: If plugin is performing the action, check if plugin has permission + if (context.getUser().isPluginUser() && this.usersToActionMatcher.containsKey(context.getUser().getName())) { + WildcardMatcher matcher = this.usersToActionMatcher.get(context.getUser().getName()); + if (matcher != null && matcher.test(action)) { + return PrivilegesEvaluatorResponse.ok(); + } + } + return PrivilegesEvaluatorResponse.insufficient(action); } @@ -476,6 +518,16 @@ PrivilegesEvaluatorResponse providesAnyPrivilege(PrivilegesEvaluationContext con } } + // 4: If plugin is performing the action, check if plugin has permission + if (this.usersToActionMatcher.containsKey(context.getUser().getName())) { + WildcardMatcher matcher = this.usersToActionMatcher.get(context.getUser().getName()); + for (String action : actions) { + if (matcher != null && matcher.test(action)) { + return PrivilegesEvaluatorResponse.ok(); + } + } + } + if (actions.size() == 1) { return PrivilegesEvaluatorResponse.insufficient(actions.iterator().next()); } else { diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 36666972ec..158a5d0a48 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; @@ -142,6 +143,7 @@ public class PrivilegesEvaluator { private final boolean checkSnapshotRestoreWritePrivileges; private final ClusterInfoHolder clusterInfoHolder; + private final ConfigurationRepository configurationRepository; private ConfigModel configModel; private final IndexResolverReplacer irr; private final SnapshotRestoreEvaluator snapshotRestoreEvaluator; @@ -152,6 +154,7 @@ public class PrivilegesEvaluator { private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; private final Settings settings; + private final Map> pluginToClusterActions; private final AtomicReference actionPrivileges = new AtomicReference<>(); public PrivilegesEvaluator( @@ -175,6 +178,7 @@ public PrivilegesEvaluator( this.threadContext = threadContext; this.privilegesInterceptor = privilegesInterceptor; + this.pluginToClusterActions = new HashMap<>(); this.clusterStateSupplier = clusterStateSupplier; this.settings = settings; @@ -191,6 +195,7 @@ public PrivilegesEvaluator( termsAggregationEvaluator = new TermsAggregationEvaluator(); pitPrivilegesEvaluator = new PitPrivilegesEvaluator(); this.namedXContentRegistry = namedXContentRegistry; + this.configurationRepository = configurationRepository; if (configurationRepository != null) { configurationRepository.subscribeOnChange(configMap -> { @@ -231,7 +236,8 @@ void updateConfiguration( DynamicConfigFactory.addStatics(rolesConfiguration.clone()), flattenedActionGroups, () -> clusterStateSupplier.get().metadata().getIndicesLookup(), - settings + settings, + pluginToClusterActions ); Metadata metadata = clusterStateSupplier.get().metadata(); actionPrivileges.updateStatefulIndexPrivileges(metadata.getIndicesLookup(), metadata.version()); @@ -838,4 +844,8 @@ private List toString(List aliases) { return Collections.unmodifiableList(ret); } + + public void updatePluginToClusterActions(String pluginIdentifier, Set clusterActions) { + pluginToClusterActions.put(pluginIdentifier, clusterActions); + } } diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 99828f7b17..f6a786c514 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -302,6 +302,33 @@ private void evaluateSystemIndicesAccess( } } + // cluster actions will return true for requestedResolved.isLocalAll() + // the following section should only be run for index actions + if (user.isPluginUser() && !requestedResolved.isLocalAll()) { + Set matchingSystemIndices = SystemIndexRegistry.matchesPluginSystemIndexPattern( + user.getName().replace("plugin:", ""), + requestedResolved.getAllIndices() + ); + if (requestedResolved.getAllIndices().equals(matchingSystemIndices)) { + // plugin is authorized to perform any actions on its own registered system indices + presponse.allowed = true; + presponse.markComplete(); + } else { + if (log.isInfoEnabled()) { + log.info( + "Plugin {} can only perform {} on it's own registered System Indices. System indices from request that match plugin's registered system indices: {}", + user.getName(), + action, + matchingSystemIndices + ); + } + presponse.allowed = false; + presponse.getMissingPrivileges(); + presponse.markComplete(); + } + return; + } + if (isActionAllowed(action)) { if (requestedResolved.isLocalAll()) { if (filterSecurityIndex) { diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 444c77f0f4..106b7451c4 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -115,6 +115,9 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_USER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user"; public static final String OPENDISTRO_SECURITY_USER_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user_header"; + // persistent header. This header is set once and cannot be stashed + public static final String OPENDISTRO_SECURITY_AUTHENTICATED_USER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "authenticated_user"; + public static final String OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user_info"; public static final String OPENDISTRO_SECURITY_INJECTED_USER = "injected_user"; diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index 6abba3d734..190729623d 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -290,10 +290,19 @@ public final Set getSecurityRoles() { /** * Check the custom attributes associated with this user * - * @return true if it has a service account attributes. otherwise false + * @return true if it has a service account attributes, otherwise false */ public boolean isServiceAccount() { Map userAttributesMap = this.getCustomAttributesMap(); return userAttributesMap != null && "true".equals(userAttributesMap.get("attr.internal.service")); } + + /** + * Check the custom attributes associated with this user + * + * @return true if it has a plugin account attributes, otherwise false + */ + public boolean isPluginUser() { + return name != null && name.startsWith("plugin:"); + } } diff --git a/src/test/java/org/opensearch/security/auth/UserSubjectImplTests.java b/src/test/java/org/opensearch/security/auth/UserSubjectImplTests.java new file mode 100644 index 0000000000..9e630ef750 --- /dev/null +++ b/src/test/java/org/opensearch/security/auth/UserSubjectImplTests.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.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.concurrent.TimeUnit; + +import org.junit.Test; + +import org.opensearch.security.user.User; +import org.opensearch.threadpool.TestThreadPool; +import org.opensearch.threadpool.ThreadPool; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_USER; +import static org.junit.Assert.assertNull; + +public class UserSubjectImplTests { + + public static boolean terminate(ThreadPool threadPool) { + return ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS); + } + + @Test + public void testSecurityUserSubjectRunAs() throws Exception { + final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + User user = new User("testUser"); + + UserSubjectImpl subject = new UserSubjectImpl(threadPool, user); + + assertThat(subject.getPrincipal().getName(), equalTo(user.getName())); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + subject.runAs(() -> { + assertThat(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER), equalTo(user)); + return null; + }); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + terminate(threadPool); + } +} diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java new file mode 100644 index 0000000000..a719fc716e --- /dev/null +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.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.identity; + +import org.junit.Test; + +import org.opensearch.common.settings.Settings; +import org.opensearch.plugins.IdentityAwarePlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.security.auth.UserSubjectImplTests; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.TestThreadPool; +import org.opensearch.threadpool.ThreadPool; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_USER; +import static org.junit.Assert.assertNull; + +public class ContextProvidingPluginSubjectTests { + static class TestIdentityAwarePlugin extends Plugin implements IdentityAwarePlugin { + + } + + @Test + public void testSecurityUserSubjectRunAs() throws Exception { + final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + final Plugin testPlugin = new TestIdentityAwarePlugin(); + + final String pluginPrincipal = "plugin:" + testPlugin.getClass().getCanonicalName(); + + final User pluginUser = new User(pluginPrincipal); + + ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); + + assertThat(subject.getPrincipal().getName(), equalTo(pluginPrincipal)); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + subject.runAs(() -> { + assertThat(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER), equalTo(pluginUser)); + return null; + }); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + UserSubjectImplTests.terminate(threadPool); + } +} From 5e5339d39b80297ca2e9411854fbca4440d5d51c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 16 Jan 2025 10:40:28 -0500 Subject: [PATCH 17/21] Cleanup SystemIndexTests with RunAsSubjectClient (#5027) Signed-off-by: Craig Perkins --- .../systemindex/SystemIndexTests.java | 2 +- .../IndexDocumentIntoSystemIndexAction.java | 7 +- .../IndexDocumentIntoSystemIndexResponse.java | 48 ------------ .../sampleplugin/PluginContextSwitcher.java | 35 --------- ...dexDocumentIntoMixOfSystemIndexAction.java | 27 +++---- ...ulkIndexDocumentIntoSystemIndexAction.java | 31 ++++---- .../RestRunClusterHealthAction.java | 4 +- .../sampleplugin/RunAsSubjectClient.java | 62 +++++++++++++++ .../sampleplugin/RunClusterHealthAction.java | 7 +- .../RunClusterHealthResponse.java | 42 ----------- .../sampleplugin/SystemIndexPlugin1.java | 16 ++-- ...ortIndexDocumentIntoSystemIndexAction.java | 75 +++++++------------ .../TransportRunClusterHealthAction.java | 54 ++++--------- 13 files changed, 150 insertions(+), 260 deletions(-) delete mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.java delete mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.java create mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunAsSubjectClient.java delete mode 100644 src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/systemindex/SystemIndexTests.java index 3435e6dbdc..13325b3029 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/SystemIndexTests.java @@ -100,7 +100,7 @@ public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() { HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_1); assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); - assertThat(response.getBody(), containsString(SystemIndexPlugin1.class.getCanonicalName())); + assertThat(response.getBody(), containsString("{\"acknowledged\":true}")); } } diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java index a78c57e1d1..3be83b39b2 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java @@ -10,13 +10,16 @@ package org.opensearch.security.systemindex.sampleplugin; +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here import org.opensearch.action.ActionType; +import org.opensearch.action.support.master.AcknowledgedResponse; +// CS-ENFORCE-SINGLE -public class IndexDocumentIntoSystemIndexAction extends ActionType { +public class IndexDocumentIntoSystemIndexAction extends ActionType { public static final IndexDocumentIntoSystemIndexAction INSTANCE = new IndexDocumentIntoSystemIndexAction(); public static final String NAME = "cluster:mock/systemindex/index"; private IndexDocumentIntoSystemIndexAction() { - super(NAME, IndexDocumentIntoSystemIndexResponse::new); + super(NAME, AcknowledgedResponse::new); } } diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.java deleted file mode 100644 index e154742fe2..0000000000 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexResponse.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.systemindex.sampleplugin; - -// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here -import java.io.IOException; - -import org.opensearch.action.support.master.AcknowledgedResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; -// CS-ENFORCE-SINGLE - -public class IndexDocumentIntoSystemIndexResponse extends AcknowledgedResponse implements ToXContentObject { - - private String plugin; - - public IndexDocumentIntoSystemIndexResponse(boolean status, String plugin) { - super(status); - this.plugin = plugin; - } - - public IndexDocumentIntoSystemIndexResponse(StreamInput in) throws IOException { - super(in); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(plugin); - } - - @Override - public void addCustomFields(XContentBuilder builder, ToXContent.Params params) throws IOException { - super.addCustomFields(builder, params); - builder.field("plugin", plugin); - } -} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.java deleted file mode 100644 index 4ef420efdb..0000000000 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/PluginContextSwitcher.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.systemindex.sampleplugin; - -import java.util.Objects; -import java.util.concurrent.Callable; - -import org.opensearch.identity.PluginSubject; - -public class PluginContextSwitcher { - private PluginSubject pluginSubject; - - public PluginContextSwitcher() {} - - public void initialize(PluginSubject pluginSubject) { - Objects.requireNonNull(pluginSubject); - this.pluginSubject = pluginSubject; - } - - public T runAs(Callable callable) { - try { - return pluginSubject.runAs(callable); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java index e9b569a263..7a9f930c2a 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -34,11 +34,11 @@ public class RestBulkIndexDocumentIntoMixOfSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginContextSwitcher contextSwitcher; + private final RunAsSubjectClient pluginClient; - public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, RunAsSubjectClient pluginClient) { this.client = client; - this.contextSwitcher = contextSwitcher; + this.pluginClient = pluginClient; } @Override @@ -57,19 +57,14 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - contextSwitcher.runAs(() -> { - BulkRequestBuilder builder = client.prepareBulk(); - builder.add(new IndexRequest(SYSTEM_INDEX_1).source("content", 1)); - builder.add(new IndexRequest(SYSTEM_INDEX_2).source("content", 1)); - builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); - BulkRequest bulkRequest = builder.request(); - client.bulk(bulkRequest, ActionListener.wrap(r -> { - channel.sendResponse( - new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) - ); - }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); - return null; - }); + BulkRequestBuilder builder = client.prepareBulk(); + builder.add(new IndexRequest(SYSTEM_INDEX_1).source("content", 1)); + builder.add(new IndexRequest(SYSTEM_INDEX_2).source("content", 1)); + builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkRequest bulkRequest = builder.request(); + pluginClient.bulk(bulkRequest, ActionListener.wrap(r -> { + channel.sendResponse(new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS))); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); } }; } diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.java index 8b37e54164..a6d729aca4 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestBulkIndexDocumentIntoSystemIndexAction.java @@ -34,11 +34,11 @@ public class RestBulkIndexDocumentIntoSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginContextSwitcher contextSwitcher; + private final RunAsSubjectClient pluginClient; - public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + public RestBulkIndexDocumentIntoSystemIndexAction(Client client, RunAsSubjectClient pluginClient) { this.client = client; - this.contextSwitcher = contextSwitcher; + this.pluginClient = pluginClient; } @Override @@ -58,21 +58,18 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - contextSwitcher.runAs(() -> { - client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { - BulkRequestBuilder builder = client.prepareBulk(); - builder.add(new IndexRequest(indexName).source("{\"content\":1}", XContentType.JSON)); - builder.add(new IndexRequest(indexName).source("{\"content\":2}", XContentType.JSON)); - builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); - BulkRequest bulkRequest = builder.request(); - client.bulk(bulkRequest, ActionListener.wrap(r2 -> { - channel.sendResponse( - new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) - ); - }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); + pluginClient.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { + BulkRequestBuilder builder = client.prepareBulk(); + builder.add(new IndexRequest(indexName).source("{\"content\":1}", XContentType.JSON)); + builder.add(new IndexRequest(indexName).source("{\"content\":2}", XContentType.JSON)); + builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkRequest bulkRequest = builder.request(); + pluginClient.bulk(bulkRequest, ActionListener.wrap(r2 -> { + channel.sendResponse( + new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) + ); }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); - return null; - }); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); } }; } diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.java index f4674b4377..232f462072 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RestRunClusterHealthAction.java @@ -24,11 +24,9 @@ public class RestRunClusterHealthAction extends BaseRestHandler { private final Client client; - private final PluginContextSwitcher contextSwitcher; - public RestRunClusterHealthAction(Client client, PluginContextSwitcher contextSwitcher) { + public RestRunClusterHealthAction(Client client) { this.client = client; - this.contextSwitcher = contextSwitcher; } @Override diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunAsSubjectClient.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunAsSubjectClient.java new file mode 100644 index 0000000000..39540841cb --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunAsSubjectClient.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.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.systemindex.sampleplugin; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionType; +import org.opensearch.client.Client; +import org.opensearch.client.FilterClient; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.identity.Subject; + +/** + * Implementation of client that will run transport actions in a stashed context and inject the name of the provided + * subject into the context. + */ +public class RunAsSubjectClient extends FilterClient { + + private static final Logger logger = LogManager.getLogger(RunAsSubjectClient.class); + + private Subject subject; + + public RunAsSubjectClient(Client delegate) { + super(delegate); + } + + public RunAsSubjectClient(Client delegate, Subject subject) { + super(delegate); + this.subject = subject; + } + + public void setSubject(Subject subject) { + this.subject = subject; + } + + @Override + protected void doExecute( + ActionType action, + Request request, + ActionListener listener + ) { + try (ThreadContext.StoredContext ctx = threadPool().getThreadContext().newStoredContext(false)) { + subject.runAs(() -> { + logger.info("Running transport action with subject: {}", subject.getPrincipal().getName()); + super.doExecute(action, request, ActionListener.runBefore(listener, ctx::restore)); + return null; + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java index dca6b8d2b7..e197362254 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java @@ -10,13 +10,16 @@ package org.opensearch.security.systemindex.sampleplugin; +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here import org.opensearch.action.ActionType; +import org.opensearch.action.support.master.AcknowledgedResponse; +// CS-ENFORCE-SINGLE -public class RunClusterHealthAction extends ActionType { +public class RunClusterHealthAction extends ActionType { public static final RunClusterHealthAction INSTANCE = new RunClusterHealthAction(); public static final String NAME = "cluster:mock/monitor/health"; private RunClusterHealthAction() { - super(NAME, RunClusterHealthResponse::new); + super(NAME, AcknowledgedResponse::new); } } diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java deleted file mode 100644 index a500755e22..0000000000 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthResponse.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.systemindex.sampleplugin; - -// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here -import java.io.IOException; - -import org.opensearch.action.support.master.AcknowledgedResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; -// CS-ENFORCE-SINGLE - -public class RunClusterHealthResponse extends AcknowledgedResponse implements ToXContentObject { - - public RunClusterHealthResponse(boolean status) { - super(status); - } - - public RunClusterHealthResponse(StreamInput in) throws IOException { - super(in); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - } - - @Override - public void addCustomFields(XContentBuilder builder, Params params) throws IOException { - super.addCustomFields(builder, params); - } -} diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.java index 9805619965..9113a50a5f 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/SystemIndexPlugin1.java @@ -45,7 +45,7 @@ public class SystemIndexPlugin1 extends Plugin implements SystemIndexPlugin, IdentityAwarePlugin { public static final String SYSTEM_INDEX_1 = ".system-index1"; - private PluginContextSwitcher contextSwitcher; + private RunAsSubjectClient pluginClient; private Client client; @@ -64,8 +64,8 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.client = client; - this.contextSwitcher = new PluginContextSwitcher(); - return List.of(contextSwitcher); + this.pluginClient = new RunAsSubjectClient(client); + return List.of(pluginClient); } @Override @@ -86,9 +86,9 @@ public List getRestHandlers( ) { return List.of( new RestIndexDocumentIntoSystemIndexAction(client), - new RestRunClusterHealthAction(client, contextSwitcher), - new RestBulkIndexDocumentIntoSystemIndexAction(client, contextSwitcher), - new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, contextSwitcher) + new RestRunClusterHealthAction(client), + new RestBulkIndexDocumentIntoSystemIndexAction(client, pluginClient), + new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, pluginClient) ); } @@ -102,8 +102,8 @@ public List getRestHandlers( @Override public void assignSubject(PluginSubject pluginSystemSubject) { - if (contextSwitcher != null) { - this.contextSwitcher.initialize(pluginSystemSubject); + if (pluginClient != null) { + this.pluginClient.setSubject(pluginSystemSubject); } } } diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java index 6138cfbb54..26ae100c89 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -10,85 +10,64 @@ package org.opensearch.security.systemindex.sampleplugin; +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.action.support.WriteRequest; +import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.client.Client; import org.opensearch.common.inject.Inject; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; -import org.opensearch.identity.IdentityService; -import org.opensearch.identity.Subject; -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.user.User; import org.opensearch.tasks.Task; -import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; +// CS-ENFORCE-SINGLE public class TransportIndexDocumentIntoSystemIndexAction extends HandledTransportAction< IndexDocumentIntoSystemIndexRequest, - IndexDocumentIntoSystemIndexResponse> { + AcknowledgedResponse> { private final Client client; - private final ThreadPool threadPool; - private final PluginContextSwitcher contextSwitcher; - private final IdentityService identityService; + private final RunAsSubjectClient pluginClient; @Inject public TransportIndexDocumentIntoSystemIndexAction( final TransportService transportService, final ActionFilters actionFilters, final Client client, - final ThreadPool threadPool, - final PluginContextSwitcher contextSwitcher, - final IdentityService identityService + final RunAsSubjectClient pluginClient ) { super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); this.client = client; - this.threadPool = threadPool; - this.contextSwitcher = contextSwitcher; - this.identityService = identityService; + this.pluginClient = pluginClient; } @Override - protected void doExecute( - Task task, - IndexDocumentIntoSystemIndexRequest request, - ActionListener actionListener - ) { + protected void doExecute(Task task, IndexDocumentIntoSystemIndexRequest request, ActionListener actionListener) { String indexName = request.getIndexName(); String runAs = request.getRunAs(); - Subject userSubject = identityService.getCurrentSubject(); try { - contextSwitcher.runAs(() -> { - client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { - if ("user".equalsIgnoreCase(runAs)) { - userSubject.runAs(() -> { - client.index( - new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .source("{\"content\":1}", XContentType.JSON), - ActionListener.wrap(r2 -> { - User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); - }, actionListener::onFailure) - ); - return null; - }); - } else { - client.index( - new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .source("{\"content\":1}", XContentType.JSON), - ActionListener.wrap(r2 -> { - User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); - }, actionListener::onFailure) - ); - } - }, actionListener::onFailure)); - return null; - }); + pluginClient.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { + if ("user".equalsIgnoreCase(runAs)) { + client.index( + new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON), + ActionListener.wrap(r2 -> { + actionListener.onResponse(new AcknowledgedResponse(true)); + }, actionListener::onFailure) + ); + } else { + pluginClient.index( + new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON), + ActionListener.wrap(r2 -> { + actionListener.onResponse(new AcknowledgedResponse(true)); + }, actionListener::onFailure) + ); + } + }, actionListener::onFailure)); } catch (Exception ex) { throw new RuntimeException("Unexpected error: " + ex.getMessage()); } diff --git a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java index 310262c947..12e17ae998 100644 --- a/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/TransportRunClusterHealthAction.java @@ -10,71 +10,49 @@ package org.opensearch.security.systemindex.sampleplugin; +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.client.Client; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; -import org.opensearch.identity.IdentityService; -import org.opensearch.identity.Subject; import org.opensearch.tasks.Task; -import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; +// CS-ENFORCE-SINGLE -public class TransportRunClusterHealthAction extends HandledTransportAction { +public class TransportRunClusterHealthAction extends HandledTransportAction { private final Client client; - private final ThreadPool threadPool; - private final PluginContextSwitcher contextSwitcher; - private final IdentityService identityService; + private final RunAsSubjectClient pluginClient; @Inject public TransportRunClusterHealthAction( final TransportService transportService, final ActionFilters actionFilters, final Client client, - final ThreadPool threadPool, - final PluginContextSwitcher contextSwitcher, - final IdentityService identityService + final RunAsSubjectClient pluginClient ) { super(RunClusterHealthAction.NAME, transportService, actionFilters, RunClusterHealthRequest::new); this.client = client; - this.threadPool = threadPool; - this.contextSwitcher = contextSwitcher; - this.identityService = identityService; + this.pluginClient = pluginClient; } @Override - protected void doExecute(Task task, RunClusterHealthRequest request, ActionListener actionListener) { + protected void doExecute(Task task, RunClusterHealthRequest request, ActionListener actionListener) { String runAs = request.getRunAs(); - if ("user".equalsIgnoreCase(runAs)) { - Subject user = identityService.getCurrentSubject(); - try { - user.runAs(() -> { - ActionListener chr = ActionListener.wrap( - r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, - actionListener::onFailure - ); - client.admin().cluster().health(new ClusterHealthRequest(), chr); - return null; - }); - } catch (Exception e) { - throw new RuntimeException(e); - } - } else if ("plugin".equalsIgnoreCase(runAs)) { - contextSwitcher.runAs(() -> { - ActionListener chr = ActionListener.wrap( - r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, - actionListener::onFailure - ); - client.admin().cluster().health(new ClusterHealthRequest(), chr); - return null; - }); + if ("plugin".equalsIgnoreCase(runAs)) { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new AcknowledgedResponse(true)); }, + actionListener::onFailure + ); + pluginClient.admin().cluster().health(new ClusterHealthRequest(), chr); } else { + // run in the authenticated user context ActionListener chr = ActionListener.wrap( - r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + r -> { actionListener.onResponse(new AcknowledgedResponse(true)); }, actionListener::onFailure ); client.admin().cluster().health(new ClusterHealthRequest(), chr); From 48be2af2bf2f7db8d89157a72265ea3dbd8572ca Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Thu, 16 Jan 2025 17:31:00 +0100 Subject: [PATCH 18/21] Fix SSL config for JDK PKCS setup (#4999) Signed-off-by: Andrey Pleskach --- .../security/ssl/config/SslCertificatesLoader.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java b/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java index a5eb7631f4..40de2f93f1 100644 --- a/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java +++ b/src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java @@ -62,6 +62,7 @@ public Tuple loadConfiguration(f final var settings = environment.settings(); final var sslConfigSettings = settings.getByPrefix(fullSslConfigSuffix); if (settings.hasValue(sslConfigSuffix + KEYSTORE_FILEPATH)) { + final var keyStorePassword = resolvePassword(sslConfigSuffix + KEYSTORE_PASSWORD, settings, DEFAULT_STORE_PASSWORD); return Tuple.tuple( environment.settings().hasValue(sslConfigSuffix + TRUSTSTORE_FILEPATH) ? buildJdkTrustStoreConfiguration( @@ -73,8 +74,12 @@ public Tuple loadConfiguration(f buildJdkKeyStoreConfiguration( sslConfigSettings, environment, - resolvePassword(sslConfigSuffix + KEYSTORE_PASSWORD, settings, DEFAULT_STORE_PASSWORD), - resolvePassword(fullSslConfigSuffix + KEYSTORE_KEY_PASSWORD, settings, DEFAULT_STORE_PASSWORD) + keyStorePassword, + resolvePassword( + fullSslConfigSuffix + KEYSTORE_KEY_PASSWORD, + settings, + keyStorePassword != null ? String.valueOf(keyStorePassword) : null + ) ) ); } else { From 36f67f02d5b3671ecfe42f8972c214da26d0ed7e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 17 Jan 2025 15:20:39 -0500 Subject: [PATCH 19/21] Adds a quick fix to flaky test and corrects run-time error (#5029) Signed-off-by: Darshit Chanpura --- .../security/DoNotFailOnForbiddenTests.java | 16 ++++++++++------ .../support/SafeSerializationUtilsTest.java | 6 ++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java index 456d1ebada..4aa6005beb 100644 --- a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java +++ b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java @@ -12,6 +12,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.stream.Collectors; @@ -462,8 +463,9 @@ public void shouldPerformCatIndices_positive() throws IOException { Request getIndicesRequest = new Request("GET", "/_cat/indices"); // High level client doesn't support _cat/_indices API Response getIndicesResponse = restHighLevelClient.getLowLevelClient().performRequest(getIndicesRequest); - List indexes = new BufferedReader(new InputStreamReader(getIndicesResponse.getEntity().getContent())).lines() - .collect(Collectors.toList()); + List indexes = new BufferedReader( + new InputStreamReader(getIndicesResponse.getEntity().getContent(), StandardCharsets.UTF_8) + ).lines().collect(Collectors.toList()); assertThat(indexes.size(), equalTo(1)); assertThat(indexes.get(0), containsString("marvelous_songs")); @@ -476,8 +478,9 @@ public void shouldPerformCatAliases_positive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) { Request getAliasesRequest = new Request("GET", "/_cat/aliases"); Response getAliasesResponse = restHighLevelClient.getLowLevelClient().performRequest(getAliasesRequest); - List aliases = new BufferedReader(new InputStreamReader(getAliasesResponse.getEntity().getContent())).lines() - .collect(Collectors.toList()); + List aliases = new BufferedReader( + new InputStreamReader(getAliasesResponse.getEntity().getContent(), StandardCharsets.UTF_8) + ).lines().collect(Collectors.toList()); // Does not fail on forbidden, but alias response only contains index which user has access to assertThat(getAliasesResponse.getStatusLine().getStatusCode(), equalTo(200)); @@ -490,8 +493,9 @@ public void shouldPerformCatAliases_positive() throws IOException { try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) { Request getAliasesRequest = new Request("GET", "/_cat/aliases"); Response getAliasesResponse = restHighLevelClient.getLowLevelClient().performRequest(getAliasesRequest); - List aliases = new BufferedReader(new InputStreamReader(getAliasesResponse.getEntity().getContent())).lines() - .collect(Collectors.toList()); + List aliases = new BufferedReader( + new InputStreamReader(getAliasesResponse.getEntity().getContent(), StandardCharsets.UTF_8) + ).lines().collect(Collectors.toList()); // Admin has access to all assertThat(getAliasesResponse.getStatusLine().getStatusCode(), equalTo(200)); diff --git a/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java b/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java index f69d4e0291..187fd8b372 100644 --- a/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java +++ b/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.regex.Pattern; +import org.junit.After; import org.junit.Test; import org.opensearch.security.auth.UserInjector; @@ -35,6 +36,11 @@ public class SafeSerializationUtilsTest { + @After + public void clearCache() { + SafeSerializationUtils.safeClassCache.clear(); + } + @Test public void testSafeClasses() { assertTrue(SafeSerializationUtils.isSafeClass(String.class)); From 00016e0ceb37377a20ad0472da008f9f950d9366 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Fri, 17 Jan 2025 16:41:22 -0500 Subject: [PATCH 20/21] Fix tests after Apache HttpClient5 / HttpCore5 update since those use deprecated APIs (#5035) Signed-off-by: Andriy Redko --- .../http/jwt/keybyoidc/KeySetRetriever.java | 3 +- .../security/auditlog/sink/WebhookSink.java | 30 +++++----- .../security/httpclient/HttpClient.java | 26 +++------ .../http/jwt/keybyoidc/MockIpdServer.java | 39 +++++++------ .../auth/http/saml/MockSamlIdpServer.java | 57 ++++++++++--------- .../InitializationIntegrationTests.java | 2 +- .../auditlog/sink/SinkProviderTLSTest.java | 2 +- .../auditlog/sink/WebhookAuditLogTest.java | 20 +++---- .../test/AbstractSecurityUnitTest.java | 13 ++--- .../security/test/helper/rest/RestHelper.java | 4 +- 10 files changed, 97 insertions(+), 99 deletions(-) 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 58eff89341..7e7f088069 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 @@ -28,6 +28,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.ssl.DefaultClientTlsStrategy; import org.apache.hc.core5.http.HttpEntity; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -223,7 +224,7 @@ private CloseableHttpClient createHttpClient(HttpCacheStorage httpCacheStorage) if (sslConfig != null) { final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()) + .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslConfig.getSslContext())) .build(); builder.setConnectionManager(cm); 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 219a7d05ac..40c278026b 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java @@ -32,14 +32,15 @@ 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.DefaultClientTlsStrategy; 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.client5.http.ssl.TrustAllStrategy; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.ssl.SSLContextBuilder; -import org.apache.hc.core5.ssl.TrustStrategy; import org.apache.http.HttpStatus; import org.opensearch.common.settings.Settings; @@ -368,27 +369,20 @@ CloseableHttpClient getHttpClient() { .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS) .build(); - final TrustStrategy trustAllStrategy = new TrustStrategy() { - @Override - public boolean isTrusted(X509Certificate[] chain, String authType) { - return true; - } - }; - try { - HttpClientBuilder hcb = HttpClients.custom().setDefaultRequestConfig(config); if (!verifySSL) { - SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(trustAllStrategy).build(); - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(TrustAllStrategy.INSTANCE).build(); + final DefaultClientTlsStrategy sslsf = new DefaultClientTlsStrategy( sslContext, null, null, + SSLBufferMode.STATIC, NoopHostnameVerifier.INSTANCE ); final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) + .setTlsSocketStrategy(sslsf) .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) .build(); hcb.setConnectionManager(cm); @@ -399,10 +393,16 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { 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 DefaultClientTlsStrategy sslsf = new DefaultClientTlsStrategy( + sslContext, + null, + null, + SSLBufferMode.STATIC, + new DefaultHostnameVerifier() + ); final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) + .setTlsSocketStrategy(sslsf) .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) .build(); hcb.setConnectionManager(cm); diff --git a/src/main/java/org/opensearch/security/httpclient/HttpClient.java b/src/main/java/org/opensearch/security/httpclient/HttpClient.java index 43b5107b70..8ce422e6e1 100644 --- a/src/main/java/org/opensearch/security/httpclient/HttpClient.java +++ b/src/main/java/org/opensearch/security/httpclient/HttpClient.java @@ -30,7 +30,6 @@ 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; @@ -38,15 +37,14 @@ 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.DefaultClientTlsStrategy; 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.reactor.ssl.SSLBufferMode; import org.apache.hc.core5.ssl.PrivateKeyDetails; import org.apache.hc.core5.ssl.PrivateKeyStrategy; import org.apache.hc.core5.ssl.SSLContextBuilder; @@ -280,19 +278,13 @@ public String chooseAlias(Map aliases, SSLParameters final HostnameVerifier hnv = verifyHostnames ? new DefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE; 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(); + final TlsStrategy tlsStrategy = new DefaultClientTlsStrategy( + sslContext, + supportedProtocols, + supportedCipherSuites, + SSLBufferMode.STATIC, + hnv + ); final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(tlsStrategy).build(); httpClientBuilder.setConnectionManager(cm); 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 703eca4550..4ec1956539 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 @@ -62,24 +62,27 @@ class MockIpdServer implements Closeable { 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); - } - }); + ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap().setListenerPort(port).setRequestRouter((request, context) -> { + if (request.getRequestUri().startsWith(CTX_DISCOVER)) { + return new HttpRequestHandler() { + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleDiscoverRequest(request, response, context); + } + }; + } else if (request.getRequestUri().startsWith(CTX_KEYS)) { + return new HttpRequestHandler() { + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleKeysRequest(request, response, context); + } + }; + } else { + return null; + } + }); if (ssl) { serverBootstrap = serverBootstrap.setSslContext(createSSLContext()).setSslSetupHandler(new Callback() { 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 1cf9205a67..e9aee68158 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 @@ -195,34 +195,35 @@ class MockSamlIdpServer implements Closeable { this.loadSigningKeys("saml/kirk-keystore.jks", "kirk"); - ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap() - .setListenerPort(port) - .register(CTX_METADATA, new HttpRequestHandler() { - - @Override - public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, - IOException { - - handleMetadataRequest(request, response, context); - - } - }) - .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 { - handleSloRequest(request, response, context); - } - }); + ServerBootstrap serverBootstrap = ServerBootstrap.bootstrap().setListenerPort(port).setRequestRouter((request, context) -> { + if (request.getRequestUri().startsWith(CTX_METADATA)) { + return new HttpRequestHandler() { + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleMetadataRequest(request, response, context); + } + }; + } else if (request.getRequestUri().startsWith(CTX_SAML_SSO)) { + return new HttpRequestHandler() { + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleSsoRequest(request, response, context); + } + }; + } else if (request.getRequestUri().startsWith(CTX_SAML_SLO)) { + return new HttpRequestHandler() { + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) throws HttpException, + IOException { + handleSloRequest(request, response, context); + } + }; + } else { + return null; + } + }); if (ssl) { diff --git a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java index a5f4ff6dba..79a32aec63 100644 --- a/src/test/java/org/opensearch/security/InitializationIntegrationTests.java +++ b/src/test/java/org/opensearch/security/InitializationIntegrationTests.java @@ -178,7 +178,7 @@ public void testWhoAmIForceHttp1() throws Exception { Response whoAmIRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_plugins/_security/whoami")); assertThat(200, is(whoAmIRes.getStatusLine().getStatusCode())); // The HTTP/1.1 is forced and should be used instead - assertThat(HttpVersion.HTTP_1_1, is(whoAmIRes.getStatusLine().getProtocolVersion())); + assertThat(whoAmIRes.getStatusLine().getProtocolVersion(), is(HttpVersion.HTTP_1_1)); JsonNode whoAmIResNode = DefaultObjectMapper.objectMapper.readTree(whoAmIRes.getEntity().getContent()); String whoAmIResponsePayload = whoAmIResNode.toPrettyString(); assertThat(whoAmIResponsePayload, whoAmIResNode.get("dn").asText(), is("CN=spock,OU=client,O=client,L=Test,C=DE")); 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 1e4f749271..aae15c2696 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java @@ -65,7 +65,7 @@ public void testTlsConfigurationNoFallback() throws Exception { .setListenerPort(port) .setHttpProcessor(HttpProcessors.server("Test/1.1")) .setSslContext(createSSLContext()) - .register("*", handler) + .setRequestRouter((request, context) -> 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 f0dfe123ae..ff8d2f2a79 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java @@ -240,7 +240,7 @@ public void postGetHttpTest() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .register("*", handler) + .setRequestRouter((request, context) -> handler) .create(); server.start(); @@ -355,7 +355,7 @@ public void httpsTestWithoutTLSServer() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .register("*", handler) + .setRequestRouter((request, context) -> handler) .create(); server.start(); @@ -393,8 +393,8 @@ public void httpsTest() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) + .setServerSocketFactory(createSSLContext().getServerSocketFactory()) + .setRequestRouter((request, context) -> handler) .create(); server.start(); @@ -481,8 +481,8 @@ public void httpsTestPemDefault() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) + .setServerSocketFactory(createSSLContext().getServerSocketFactory()) + .setRequestRouter((request, context) -> handler) .create(); server.start(); @@ -610,8 +610,8 @@ public void httpsTestPemEndpoint() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) + .setServerSocketFactory(createSSLContext().getServerSocketFactory()) + .setRequestRouter((request, context) -> handler) .create(); server.start(); @@ -717,8 +717,8 @@ public void httpsTestPemContentEndpoint() throws Exception { server = ServerBootstrap.bootstrap() .setListenerPort(port) .setHttpProcessor(HttpProcessors.server("Test/1.1")) - .setSslContext(createSSLContext()) - .register("*", handler) + .setServerSocketFactory(createSSLContext().getServerSocketFactory()) + .setRequestRouter((request, context) -> handler) .create(); server.start(); diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index ec4525eb30..aa134ad1e1 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -44,8 +44,8 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope.Scope; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import org.apache.hc.client5.http.config.TlsConfig; 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; @@ -197,14 +197,13 @@ public TlsDetails create(final SSLEngine sslEngine) { }) .build(); - final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(tlsStrategy) - .build(); - builder.setConnectionManager(cm); + final PoolingAsyncClientConnectionManagerBuilder cm = PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy); + if (httpVersionPolicy != null) { - builder.setVersionPolicy(httpVersionPolicy); + cm.setDefaultTlsConfig(TlsConfig.custom().setVersionPolicy(httpVersionPolicy).build()); } - return builder; + return builder.setConnectionManager(cm.build()); }); return new RestHighLevelClient(restClientBuilder); } catch (Exception 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 1710a93875..81dd89badf 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 @@ -381,7 +381,9 @@ public TlsDetails create(final SSLEngine sslEngine) { hcb.setConnectionManager(cm); } - final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom().setResponseTimeout(Timeout.ofSeconds(60)); + final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom() + .setResponseTimeout(Timeout.ofSeconds(60)) + .setProtocolUpgradeEnabled(false); return hcb.setDefaultRequestConfig(requestConfigBuilder.build()).disableAutomaticRetries().build(); } From c84caef15f6935594548586899470c4802bd161c Mon Sep 17 00:00:00 2001 From: Derek Ho Date: Fri, 17 Jan 2025 17:01:09 -0500 Subject: [PATCH 21/21] Move to upload artifact v4 for BWC action (#5034) Signed-off-by: Derek Ho --- .github/actions/run-bwc-suite/action.yaml | 2 +- .github/workflows/integration-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/run-bwc-suite/action.yaml b/.github/actions/run-bwc-suite/action.yaml index f05696699c..12ebf13a5d 100644 --- a/.github/actions/run-bwc-suite/action.yaml +++ b/.github/actions/run-bwc-suite/action.yaml @@ -50,7 +50,7 @@ runs: -Dbwc.version.previous=${{ steps.build-previous.outputs.built-version }} -Dbwc.version.next=${{ steps.build-next.outputs.built-version }} -i - - uses: alehechka/upload-tartifact@v2 + - uses: actions/upload-artifact@v4 if: always() with: name: ${{ inputs.report-artifact-name }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index cf3904c8cc..428b4feb48 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -24,7 +24,7 @@ jobs: - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test - - uses: alehechka/upload-tartifact@v2 + - uses: actions/upload-artifact@v4 if: always() with: name: ${{ matrix.jdk }}-${{ matrix.test-run }}-reports