From 65c5b69615609bd93da6495e0412e8584db12819 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 19 Mar 2024 08:51:53 -0400 Subject: [PATCH 01/19] Bump org.apache.zookeeper:zookeeper from 3.9.1. to 3.9.2 (#4129) ### Description Bumps org.apache.zookeeper:zookeeper from 3.9.1. to 3.9.2 Resolves WhiteSource Security check seen on byte-buddy upgrade: https://github.com/opensearch-project/security/pull/4127/checks?check_run_id=22820167922 * Category Maintenance ### Check List - [ ] New functionality includes testing - [ ] New functionality has been documented - [ ] Commits are signed per the DCO using --signoff By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). Signed-off-by: Craig Perkins --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5b145b9412..e63135a98a 100644 --- a/build.gradle +++ b/build.gradle @@ -706,7 +706,7 @@ dependencies { testRuntimeOnly 'org.scala-lang:scala-library:2.13.13' testRuntimeOnly 'com.yammer.metrics:metrics-core:2.2.0' testRuntimeOnly 'com.typesafe.scala-logging:scala-logging_3:3.9.5' - testRuntimeOnly('org.apache.zookeeper:zookeeper:3.9.1') { + testRuntimeOnly('org.apache.zookeeper:zookeeper:3.9.2') { exclude(group:'ch.qos.logback', module: 'logback-classic' ) exclude(group:'ch.qos.logback', module: 'logback-core' ) } From 582d2cddbe3c23640370698647752b39ff58eed0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:08:30 +0100 Subject: [PATCH 02/19] Bump com.google.errorprone:error_prone_annotations from 2.25.0 to 2.26.1 (#4126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [com.google.errorprone:error_prone_annotations](https://github.com/google/error-prone) from 2.25.0 to 2.26.1.
Release notes

Sourced from com.google.errorprone:error_prone_annotations's releases.

Error Prone 2.26.1

This release contains all of the changes in 2.26.0, plus a bug fix to the module name of the annotations artifact com.google.errorprone.annotations (https://github.com/google/error-prone/commit/9d99ee76f2ca8568b69150f5df7fe845c8545d16)

Starting in 2.26.x, the 'annotations' artifact now includes a module-info.java for Java Platform Module System support, thanks to @​sgammon in #4311.


Compatibility note:

Now that the annotations artifact explicit declares a module instead of relying on Automatic-Module-Name, JDK 17 and newer perform stricter module encapsulation checks. Modularized libraries depending on Error Prone annotations 2.26.x and newer may see errors like:

error: package com.google.errorprone.annotations is not
visible
import com.google.errorprone.annotations.CheckReturnValue;
                            ^
(package com.google.errorprone.annotations is declared in module
com.google.errorprone.annotations, but module ... does not read it)

The fix is to add requires static to the module declaration of modularized libraries that depend on Error Prone annotations:

 module your.module {
...
+  requires static com.google.errorprone.annotations;
 }

Full Changelog: https://github.com/google/error-prone/compare/v2.26.0...v2.26.1

Error Prone 2.26.0

Warning: This release contains a bug, please use 2.26.1 or newer instead.

Changes:

  • The 'annotations' artifact now includes a module-info.java for Java Platform Module System support, thanks to @​sgammon in #4311.
  • Disabled checks passed to -XepPatchChecks are now ignored, instead of causing a crash. Thanks to @​oxkitsune in #4028.

New checks:

  • SystemConsoleNull: Null-checking System.console() is not a reliable way to detect if the console is connected to a terminal.
  • EnumOrdinal: Discourage uses of Enum.ordinal()

Closed issues: #2649, #3908, #4028, #4311, #4314

Full Changelog: https://github.com/google/error-prone/compare/v2.25.0...v2.26.0

Commits
  • b380572 Release Error Prone 2.26.1
  • 9d99ee7 fix: module name → com.google.errorprone.annotations
  • ea5ef6d Add the 'compile' goal for 'compile-java9'
  • 0e95364 feat: add jpms definition for annotations
  • 9da2d55 Ignore disabled checks passed to -XepPatchChecks
  • 3292632 Increase year range on Date usages.
  • ad513d5 Recommend using var for var unused = ...; and `var thrown = assertThrows(...
  • af37d35 ImpossibleNullComparison: emit empty fixes.
  • 297019c Fix some mistakes in the EnumOrdinal examples
  • f3dbb09 Move the EnumOrdinal.md doc to the right place (it got overwritten by automat...
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.google.errorprone:error_prone_annotations&package-manager=gradle&previous-version=2.25.0&new-version=2.26.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 e63135a98a..8df9a52212 100644 --- a/build.gradle +++ b/build.gradle @@ -499,7 +499,7 @@ configurations { // For integrationTest force "org.apache.httpcomponents:httpclient:4.5.14" force "org.apache.httpcomponents:httpcore:4.4.16" - force "com.google.errorprone:error_prone_annotations:2.25.0" + force "com.google.errorprone:error_prone_annotations:2.26.1" force "org.checkerframework:checker-qual:3.42.0" force "ch.qos.logback:logback-classic:1.5.3" } @@ -609,7 +609,7 @@ dependencies { runtimeOnly 'com.eclipsesource.minimal-json:minimal-json:0.9.5' runtimeOnly 'commons-codec:commons-codec:1.16.1' runtimeOnly 'org.cryptacular:cryptacular:1.2.6' - compileOnly 'com.google.errorprone:error_prone_annotations:2.25.0' + compileOnly 'com.google.errorprone:error_prone_annotations:2.26.1' runtimeOnly 'com.sun.istack:istack-commons-runtime:4.2.0' runtimeOnly 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.2' runtimeOnly 'org.ow2.asm:asm:9.6' From e2893639b32d4a7cf22fafb721cfc48c72591488 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:17:37 +0000 Subject: [PATCH 03/19] Bump org.eclipse.platform:org.eclipse.core.runtime from 3.30.0 to 3.31.0 (#4122) Bumps [org.eclipse.platform:org.eclipse.core.runtime](https://github.com/eclipse-platform/eclipse.platform) from 3.30.0 to 3.31.0.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.eclipse.platform:org.eclipse.core.runtime&package-manager=gradle&previous-version=3.30.0&new-version=3.31.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 8df9a52212..27061092f5 100644 --- a/build.gradle +++ b/build.gradle @@ -494,7 +494,7 @@ configurations { force "org.apache.commons:commons-lang3:${versions.commonslang}" // for spotless transitive dependency CVE - force "org.eclipse.platform:org.eclipse.core.runtime:3.30.0" + force "org.eclipse.platform:org.eclipse.core.runtime:3.31.0" // For integrationTest force "org.apache.httpcomponents:httpclient:4.5.14" From 8ccee785d88e43d8f2090cc2d8ba728e75ed4908 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:18:07 +0000 Subject: [PATCH 04/19] Bump apache_cxf_version from 4.0.3 to 4.0.4 (#4124) Bumps `apache_cxf_version` from 4.0.3 to 4.0.4. Updates `org.apache.cxf:cxf-core` from 4.0.3 to 4.0.4 Updates `org.apache.cxf:cxf-rt-rs-json-basic` from 4.0.3 to 4.0.4 Updates `org.apache.cxf:cxf-rt-security` from 4.0.3 to 4.0.4 Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 27061092f5..6513ca87d6 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ buildscript { common_utils_version = System.getProperty("common_utils.version", '3.0.0.0-SNAPSHOT') kafka_version = '3.7.0' - apache_cxf_version = '4.0.3' + apache_cxf_version = '4.0.4' open_saml_version = '4.3.0' one_login_java_saml = '2.9.0' jjwt_version = '0.12.5' From 6d2fb70cec1bc3b6aa6ec1e5b218668d7abb10bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:18:49 +0100 Subject: [PATCH 05/19] Bump spring_version from 5.3.32 to 5.3.33 (#4125) Bumps `spring_version` from 5.3.32 to 5.3.33. Updates `org.springframework:spring-beans` from 5.3.32 to 5.3.33
Release notes

Sourced from org.springframework:spring-beans's releases.

v5.3.33

:star: New Features

  • Extract reusable method for URI validations #32442
  • Allow UriTemplate to be built with an empty template #32438
  • Refine *HttpMessageConverter#getContentLength return value null safety #32332

:lady_beetle: Bug Fixes

  • AopUtils.getMostSpecificMethod does not return original method for proxy-derived method anymore #32369
  • Better protect against concurrent error handling for async requests #32342
  • Restore Jetty 10 compatibility in JettyClientHttpResponse #32337
  • ContentCachingResponseWrapper no longer honors Content-Type and Content-Length #32322

:notebook_with_decorative_cover: Documentation

  • Build KDoc against 5.3.x Spring Framework Javadoc #32414

:hammer: Dependency Upgrades

  • Upgrade to Reactor 2020.0.42 #32422
Commits
  • df041ba Release v5.3.33
  • 297cbae Extract reusable checkSchemeAndPort method
  • 274fba4 Additional unit tests for operations on empty UriTemplate
  • 5dfec09 Allow UriTemplate to be built with an empty template
  • 5056e8c Upgrade to Reactor 2020.0.42
  • 4566e86 Polishing
  • 1b84f97 Disable external Javadoc URLs not supported on JDK 8
  • 41bc43b Build KDoc against 5.3.x Spring Framework Javadoc
  • 915d5bd Polishing
  • dc86fea Remove IOException that's not thrown from Javadoc
  • Additional commits viewable in compare view

Updates `org.springframework:spring-core` from 5.3.32 to 5.3.33
Release notes

Sourced from org.springframework:spring-core's releases.

v5.3.33

:star: New Features

  • Extract reusable method for URI validations #32442
  • Allow UriTemplate to be built with an empty template #32438
  • Refine *HttpMessageConverter#getContentLength return value null safety #32332

:lady_beetle: Bug Fixes

  • AopUtils.getMostSpecificMethod does not return original method for proxy-derived method anymore #32369
  • Better protect against concurrent error handling for async requests #32342
  • Restore Jetty 10 compatibility in JettyClientHttpResponse #32337
  • ContentCachingResponseWrapper no longer honors Content-Type and Content-Length #32322

:notebook_with_decorative_cover: Documentation

  • Build KDoc against 5.3.x Spring Framework Javadoc #32414

:hammer: Dependency Upgrades

  • Upgrade to Reactor 2020.0.42 #32422
Commits
  • df041ba Release v5.3.33
  • 297cbae Extract reusable checkSchemeAndPort method
  • 274fba4 Additional unit tests for operations on empty UriTemplate
  • 5dfec09 Allow UriTemplate to be built with an empty template
  • 5056e8c Upgrade to Reactor 2020.0.42
  • 4566e86 Polishing
  • 1b84f97 Disable external Javadoc URLs not supported on JDK 8
  • 41bc43b Build KDoc against 5.3.x Spring Framework Javadoc
  • 915d5bd Polishing
  • dc86fea Remove IOException that's not thrown from Javadoc
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 6513ca87d6..faee33aa5c 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ buildscript { jjwt_version = '0.12.5' guava_version = '32.1.3-jre' jaxb_version = '2.3.9' - spring_version = '5.3.32' + spring_version = '5.3.33' if (buildVersionQualifier) { opensearch_build += "-${buildVersionQualifier}" From 9806699cc727489c8d46637986f7860e81ed7066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:19:02 +0100 Subject: [PATCH 06/19] Bump org.awaitility:awaitility from 4.2.0 to 4.2.1 (#4123) Bumps [org.awaitility:awaitility](https://github.com/awaitility/awaitility) from 4.2.0 to 4.2.1.
Changelog

Sourced from org.awaitility:awaitility's changelog.

Changelog 4.2.1 (2024-03-15)

  • Upgraded Kotlin to 1.9.22

  • Added extension properties forever, then, and, given to the Kotlin extension. This allows you to do e.g.:

    await.forever until { .. }

  • Added shortcut for enabling logging. Before you had to do e.g.

    await() .with() .conditionEvaluationListener(new ConditionEvaluationLogger(log::info)) .pollInterval(ONE_HUNDRED_MILLISECONDS) .until(logs::size, is(4));

    You can now instead use the "logging" shortcut:

    await() .with() .logging(log::info) .pollInterval(ONE_HUNDRED_MILLISECONDS) .until(logs::size, is(4));

    or simply ".logging()" for "System.out".

    This shortcut has also been added globally:

    Awaitility.setLogging(log::info);

    or

    Awaitility.setDefaultLogging();

  • Improved lambda detection for Java 17 and Java 21

  • Upgraded Groovy to 4.0.19

Commits
  • ff13b72 [maven-release-plugin] prepare release awaitility-4.2.1
  • f80c299 [ci skip] Preparing changelog for release
  • 4be5236 [ci skip] Fixed typo in changelog
  • e15b975 Fixed failing tests
  • 7f7656e Adding 17 and 21 to tests
  • 32eafb6 Improved lambda detection and upgraded groovy/scala
  • 8012936 Trying to fix failing test
  • b01855d Revert "Added java 21 tests"
  • 0e7dff0 Revert "Revert "Use Duration factories in Durations.java (#268)""
  • 97076a9 Added java 21 tests
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.awaitility:awaitility&package-manager=gradle&previous-version=4.2.0&new-version=4.2.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 faee33aa5c..7fcd272c32 100644 --- a/build.gradle +++ b/build.gradle @@ -688,7 +688,7 @@ dependencies { testImplementation "org.springframework:spring-beans:${spring_version}" testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' - testImplementation('org.awaitility:awaitility:4.2.0') { + testImplementation('org.awaitility:awaitility:4.2.1') { exclude(group: 'org.hamcrest', module: 'hamcrest') } // Only osx-x86_64, osx-aarch_64, linux-x86_64, linux-aarch_64, windows-x86_64 are available @@ -731,7 +731,7 @@ dependencies { integrationTestImplementation 'org.hamcrest:hamcrest:2.2' integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}" - integrationTestImplementation('org.awaitility:awaitility:4.2.0') { + integrationTestImplementation('org.awaitility:awaitility:4.2.1') { exclude(group: 'org.hamcrest', module: 'hamcrest') } integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14' From 343fc7764afa84c6dab5022730edb43443bef64f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:39:33 +0000 Subject: [PATCH 07/19] Bump derek-ho/start-opensearch from 2 to 3 (#4120) Bumps [derek-ho/start-opensearch](https://github.com/derek-ho/start-opensearch) from 2 to 3.
Release notes

Sourced from derek-ho/start-opensearch's releases.

Release v3

Allow port flexibility

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=derek-ho/start-opensearch&package-manager=github_actions&previous-version=2&new-version=3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/plugin_install.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml index fb86b915e0..6fa8c74beb 100644 --- a/.github/workflows/plugin_install.yml +++ b/.github/workflows/plugin_install.yml @@ -40,7 +40,7 @@ jobs: shell: bash - name: Run Opensearch with A Single Plugin - uses: derek-ho/start-opensearch@v2 + uses: derek-ho/start-opensearch@v3 with: opensearch-version: ${{ env.OPENSEARCH_VERSION }} plugins: "file:$(pwd)/${{ env.PLUGIN_NAME }}.zip" From 32ba887290161eebbdd0788ffc02c0fb9d1b38c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 09:41:58 -0400 Subject: [PATCH 08/19] Bump Wandalen/wretry.action from 1.4.8 to 1.4.10 (#4121) Bumps [Wandalen/wretry.action](https://github.com/wandalen/wretry.action) from 1.4.8 to 1.4.10.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Wandalen/wretry.action&package-manager=github_actions&previous-version=1.4.8&new-version=1.4.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 c2e97f8e3a..0595106ce7 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@v1.4.8 + uses: Wandalen/wretry.action@v1.4.10 with: attempt_limit: 5 attempt_delay: 2000 From 25e2e51edecdd1a436223a2bae5fab8e975d2069 Mon Sep 17 00:00:00 2001 From: David Osorno <48450162+davidosorno@users.noreply.github.com> Date: Tue, 19 Mar 2024 08:57:45 -0700 Subject: [PATCH 09/19] Dynamic sign in options (#3869) ### Description **New Feature** Allow admins to define the sign-in options that will be displayed on OpenSearch Dashboard login page. There are couple of sign-in options defined in [Security documentation](https://opensearch.org/docs/latest/security/configuration/multi-auth/), and theses options must be available in security _config.yml_ file to be able to change them dynamically in Security Dashboard. Furthermore, if `anonymous_auth_enabled` is true it will be available in Security Dashboard sign-in options to allow admins enable or disable it. *Old Behavior* Admins have to update _opensearch_dashboards.yml_ adding or removing sign-in options, and then restart Dashboards to be able to log in using other sign-in option. *New Behavior* Admins can change sign-in options dynamically without having to restart the Dashboards, and the changes are applied immediately. Users just need to logout in order to see the sign-in options available. ### Issues Resolved - Related https://github.com/opensearch-project/security-dashboards-plugin/issues/1573 ### Testing Unit Testing, Integration Testing, and Manual Testing. ### Check List - [x] New functionality includes testing - [ ] New functionality has been documented - [x] Commits are signed per the DCO using --signoff By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). --------- Signed-off-by: David Osorno --- .../security/api/DashboardsInfoTest.java | 12 ++++ .../rest/api/MultiTenancyConfigApiAction.java | 31 +++++++++- .../privileges/PrivilegesEvaluator.java | 5 ++ .../security/rest/DashboardsInfoAction.java | 1 + .../securityconf/DynamicConfigModel.java | 3 + .../securityconf/DynamicConfigModelV6.java | 6 ++ .../securityconf/DynamicConfigModelV7.java | 6 ++ .../impl/DashboardSignInOption.java | 29 +++++++++ .../securityconf/impl/v6/ConfigV6.java | 7 +++ .../securityconf/impl/v7/ConfigV7.java | 8 +++ .../rest/api/MultiTenancyConfigApiTest.java | 61 +++++++++++++++++++ .../securityconf/impl/v6/ConfigV6Test.java | 8 +++ .../securityconf/impl/v7/ConfigV7Test.java | 8 +++ src/test/resources/restapi/config.yml | 12 ++++ .../restapi/securityconfig_nondefault.json | 3 +- 15 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/opensearch/security/securityconf/impl/DashboardSignInOption.java diff --git a/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java b/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java index 8bfcd3b8a8..15634e8890 100644 --- a/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; import org.opensearch.test.framework.cluster.ClusterManager; @@ -25,6 +26,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.opensearch.security.rest.DashboardsInfoAction.DEFAULT_PASSWORD_MESSAGE; import static org.opensearch.security.rest.DashboardsInfoAction.DEFAULT_PASSWORD_REGEX; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; @@ -53,4 +55,14 @@ public void testDashboardsInfoValidationMessage() throws Exception { assertThat(response.getTextFromJsonBody("/password_validation_regex"), equalTo(DEFAULT_PASSWORD_REGEX)); } } + + @Test + public void testDashboardsInfoContainsSignInOptions() throws Exception { + + try (TestRestClient client = cluster.getRestClient(DASHBOARDS_USER)) { + TestRestClient.HttpResponse response = client.get("_plugins/_security/dashboardsinfo"); + assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + assertThat(response.getTextArrayFromJsonBody("/sign_in_options").contains(DashboardSignInOption.BASIC.toString()), is(true)); + } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java index d56025aec1..2be5778956 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.IntStream; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -33,8 +34,10 @@ import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.securityconf.impl.v7.ConfigV7; +import org.opensearch.security.securityconf.impl.v7.ConfigV7.Authc; import org.opensearch.security.support.ConfigConstants; import org.opensearch.threadpool.ThreadPool; @@ -49,6 +52,7 @@ public class MultiTenancyConfigApiAction extends AbstractApiAction { public static final String DEFAULT_TENANT_JSON_PROPERTY = "default_tenant"; public static final String PRIVATE_TENANT_ENABLED_JSON_PROPERTY = "private_tenant_enabled"; public static final String MULTITENANCY_ENABLED_JSON_PROPERTY = "multitenancy_enabled"; + public static final String SIGN_IN_OPTIONS = "sign_in_options"; private static final List ROUTES = addRoutesPrefix( ImmutableList.of(new Route(GET, "/tenancy/config"), new Route(PUT, "/tenancy/config")) @@ -119,7 +123,9 @@ public Map allowedKeys() { PRIVATE_TENANT_ENABLED_JSON_PROPERTY, DataType.BOOLEAN, MULTITENANCY_ENABLED_JSON_PROPERTY, - DataType.BOOLEAN + DataType.BOOLEAN, + SIGN_IN_OPTIONS, + DataType.ARRAY ); } }); @@ -132,6 +138,7 @@ private ToXContent multitenancyContent(final ConfigV7 config) { .field(DEFAULT_TENANT_JSON_PROPERTY, config.dynamic.kibana.default_tenant) .field(PRIVATE_TENANT_ENABLED_JSON_PROPERTY, config.dynamic.kibana.private_tenant_enabled) .field(MULTITENANCY_ENABLED_JSON_PROPERTY, config.dynamic.kibana.multitenancy_enabled) + .field(SIGN_IN_OPTIONS, config.dynamic.kibana.sign_in_options) .endObject(); } @@ -177,6 +184,12 @@ private void updateAndValidatesValues(final ConfigV7 config, final JsonNode json if (Objects.nonNull(jsonContent.findValue(MULTITENANCY_ENABLED_JSON_PROPERTY))) { config.dynamic.kibana.multitenancy_enabled = jsonContent.findValue(MULTITENANCY_ENABLED_JSON_PROPERTY).asBoolean(); } + if (jsonContent.hasNonNull(SIGN_IN_OPTIONS) && jsonContent.findValue(SIGN_IN_OPTIONS).isEmpty() == false) { + JsonNode newOptions = jsonContent.findValue(SIGN_IN_OPTIONS); + List options = getNewSignInOptions(newOptions, config.dynamic.authc); + config.dynamic.kibana.sign_in_options = options; + } + final String defaultTenant = Optional.ofNullable(config.dynamic.kibana.default_tenant).map(String::toLowerCase).orElse(""); if (!config.dynamic.kibana.private_tenant_enabled && ConfigConstants.TENANCY_PRIVATE_TENANT_NAME.equals(defaultTenant)) { @@ -202,4 +215,20 @@ private void updateAndValidatesValues(final ConfigV7 config, final JsonNode json } } + private List getNewSignInOptions(JsonNode newOptions, Authc authc) { + + Set domains = authc.getDomains().keySet(); + + return IntStream.range(0, newOptions.size()).mapToObj(newOptions::get).map(JsonNode::asText).filter(option -> { + // Checking if the new sign-in options are set in backend. + if (option.equals(DashboardSignInOption.ANONYMOUS.toString()) + || domains.stream().anyMatch(domain -> domain.contains(option.toLowerCase()))) { + return true; + } else { + throw new IllegalArgumentException( + "Validation failure: " + option.toUpperCase() + " authentication provider is not available for this cluster." + ); + } + }).map(DashboardSignInOption::valueOf).collect(Collectors.toList()); + } } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 0f8d132e3e..06e41a2aaa 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -90,6 +90,7 @@ import org.opensearch.security.securityconf.ConfigModel; import org.opensearch.security.securityconf.DynamicConfigModel; import org.opensearch.security.securityconf.SecurityRoles; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; import org.opensearch.security.user.User; @@ -620,6 +621,10 @@ public String dashboardsOpenSearchRole() { return dcm.getDashboardsOpenSearchRole(); } + public List getSignInOptions() { + return dcm.getSignInOptions(); + } + private Set evaluateAdditionalIndexPermissions(final ActionRequest request, final String originalAction) { // --- check inner bulk requests final Set additionalPermissionsRequired = new HashSet<>(); diff --git a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java index 2b286d0c3d..070648ed92 100644 --- a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java @@ -108,6 +108,7 @@ public void accept(RestChannel channel) throws Exception { builder.field("multitenancy_enabled", evaluator.multitenancyEnabled()); builder.field("private_tenant_enabled", evaluator.privateTenantEnabled()); builder.field("default_tenant", evaluator.dashboardsDefaultTenant()); + builder.field("sign_in_options", evaluator.getSignInOptions()); builder.field( "password_validation_error_message", client.settings().get(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, DEFAULT_PASSWORD_MESSAGE) diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java index e3d10878da..064f555a75 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java @@ -52,6 +52,7 @@ import org.opensearch.security.http.HTTPClientCertAuthenticator; import org.opensearch.security.http.HTTPProxyAuthenticator; import org.opensearch.security.http.proxy.HTTPExtendedProxyAuthenticator; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; public abstract class DynamicConfigModel { @@ -105,6 +106,8 @@ public abstract class DynamicConfigModel { public abstract Multimap> getAuthBackendClientBlockRegistries(); + public abstract List getSignInOptions(); + public abstract Settings getDynamicOnBehalfOfSettings(); protected final Map authImplMap = new HashMap<>(); diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java index b652893bdd..c7edaf938c 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java @@ -54,6 +54,7 @@ import org.opensearch.security.auth.HTTPAuthenticator; import org.opensearch.security.auth.blocking.ClientBlockRegistry; import org.opensearch.security.auth.internal.InternalAuthenticationBackend; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.securityconf.impl.v6.ConfigV6; import org.opensearch.security.securityconf.impl.v6.ConfigV6.Authc; import org.opensearch.security.securityconf.impl.v6.ConfigV6.AuthcDomain; @@ -205,6 +206,11 @@ public Multimap> getAuthBackendClientBlockRe return Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries); } + @Override + public List getSignInOptions() { + return config.dynamic.kibana.sign_in_options; + } + @Override public Settings getDynamicOnBehalfOfSettings() { return Settings.EMPTY; diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java index 91bb59db64..ca237bc054 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java @@ -60,6 +60,7 @@ import org.opensearch.security.auth.internal.NoOpAuthenticationBackend; import org.opensearch.security.configuration.ClusterInfoHolder; import org.opensearch.security.http.OnBehalfOfAuthenticator; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.securityconf.impl.v7.ConfigV7; import org.opensearch.security.securityconf.impl.v7.ConfigV7.Authc; import org.opensearch.security.securityconf.impl.v7.ConfigV7.AuthcDomain; @@ -221,6 +222,11 @@ public Multimap> getAuthBackendClientBlockRe return Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries); } + @Override + public List getSignInOptions() { + return config.dynamic.kibana.sign_in_options; + } + @Override public Settings getDynamicOnBehalfOfSettings() { return Settings.builder() diff --git a/src/main/java/org/opensearch/security/securityconf/impl/DashboardSignInOption.java b/src/main/java/org/opensearch/security/securityconf/impl/DashboardSignInOption.java new file mode 100644 index 0000000000..3d9fa20e97 --- /dev/null +++ b/src/main/java/org/opensearch/security/securityconf/impl/DashboardSignInOption.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.securityconf.impl; + +public enum DashboardSignInOption { + BASIC("basic"), + SAML("saml"), + OPENID("openid"), + ANONYMOUS("anonymous"); + + private String option; + + DashboardSignInOption(String option) { + this.option = option; + } + + public String getOption() { + return option; + } +} diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java index c5b954675b..78758e0603 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java @@ -27,8 +27,10 @@ package org.opensearch.security.securityconf.impl.v6; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -43,6 +45,7 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auth.internal.InternalAuthenticationBackend; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.setting.DeprecatedSettings; public class ConfigV6 { @@ -100,6 +103,8 @@ public static class Kibana { public String opendistro_role = null; public String index = ".kibana"; public boolean do_not_fail_on_forbidden; + @JsonInclude(JsonInclude.Include.NON_NULL) + public List sign_in_options = Arrays.asList(DashboardSignInOption.BASIC); @Override public String toString() { @@ -113,6 +118,8 @@ public String toString() { + index + ", do_not_fail_on_forbidden=" + do_not_fail_on_forbidden + + ", sign_in_options=" + + sign_in_options + "]"; } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java index 4028719379..dc9be395b1 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java @@ -27,8 +27,10 @@ package org.opensearch.security.securityconf.impl.v7; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -44,6 +46,7 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auth.internal.InternalAuthenticationBackend; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.securityconf.impl.v6.ConfigV6; import org.opensearch.security.setting.DeprecatedSettings; @@ -76,6 +79,7 @@ public ConfigV7(ConfigV6 c6) { dynamic.kibana.private_tenant_enabled = true; dynamic.kibana.default_tenant = ""; dynamic.kibana.server_username = c6.dynamic.kibana.server_username; + dynamic.kibana.sign_in_options = c6.dynamic.kibana.sign_in_options; dynamic.http = new Http(); @@ -168,6 +172,8 @@ public static class Kibana { public String server_username = "kibanaserver"; public String opendistro_role = null; public String index = ".kibana"; + @JsonInclude(JsonInclude.Include.NON_NULL) + public List sign_in_options = Arrays.asList(DashboardSignInOption.BASIC); @Override public String toString() { @@ -183,6 +189,8 @@ public String toString() { + opendistro_role + ", index=" + index + + ", sign_in_options=" + + sign_in_options + "]"; } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java index 8438338869..752335b802 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiTest.java @@ -15,10 +15,13 @@ import org.apache.http.HttpStatus; import org.junit.Test; +import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.StringContains.containsString; @@ -54,9 +57,43 @@ private void verifyTenantUpdate(final Header... header) throws Exception { setPrivateTenantAsDefaultResponse.getStatusCode(), equalTo(HttpStatus.SC_OK) ); + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), hasItem(DashboardSignInOption.BASIC.toString())); + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), not(hasItem(DashboardSignInOption.SAML.toString()))); + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), not(hasItem(DashboardSignInOption.OPENID.toString()))); + + final HttpResponse updateDashboardSignInOptions = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"sign_in_options\": [\"BASIC\", \"OPENID\"]}", + header + ); + assertThat(updateDashboardSignInOptions.getBody(), updateDashboardSignInOptions.getStatusCode(), equalTo(HttpStatus.SC_OK)); + getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", ADMIN_FULL_ACCESS_USER); assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); assertThat(getDashboardsinfoResponse.findValueInJson("default_tenant"), equalTo("Private")); + + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), hasItem((DashboardSignInOption.BASIC.toString()))); + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), hasItem((DashboardSignInOption.OPENID.toString()))); + + final HttpResponse updateUnavailableSignInOption = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"sign_in_options\": [\"BASIC\", \"SAML\"]}", + header + ); + assertThat(updateUnavailableSignInOption.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat( + updateUnavailableSignInOption.findValueInJson("error.reason"), + containsString("Validation failure: SAML authentication provider is not available for this cluster.") + ); + + // Ensuring the sign in options array has not been modified due to the Bad Request response. + getDashboardsinfoResponse = rh.executeGetRequest("/_plugins/_security/dashboardsinfo", ADMIN_FULL_ACCESS_USER); + assertThat(getDashboardsinfoResponse.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options").size(), equalTo(2)); + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), hasItem(DashboardSignInOption.BASIC.toString())); + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), hasItem(DashboardSignInOption.OPENID.toString())); + assertThat(getDashboardsinfoResponse.findArrayInJson("sign_in_options"), not(hasItem(DashboardSignInOption.SAML.toString()))); } @Test @@ -148,6 +185,30 @@ private void verifyTenantUpdateFailed(final Header... header) throws Exception { setRandomStringAsDefaultTenant.findValueInJson("error.reason"), containsString("Default tenant should be selected from one of the available tenants.") ); + + final HttpResponse signInOptionsNonArrayValue = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"sign_in_options\": \"BASIC\"}", + header + ); + assertThat(signInOptionsNonArrayValue.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat( + signInOptionsNonArrayValue.getBody(), + signInOptionsNonArrayValue.findValueInJson("reason"), + containsString("Wrong datatype") + ); + + final HttpResponse invalidSignInOption = rh.executePutRequest( + "/_plugins/_security/api/tenancy/config", + "{\"sign_in_options\": [\"INVALID_OPTION\"]}", + header + ); + assertThat(invalidSignInOption.getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + assertThat( + invalidSignInOption.getBody(), + invalidSignInOption.findValueInJson("error.reason"), + containsString("authentication provider is not available for this cluster") + ); } @Test diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java b/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java index 2983fc6064..a780b0066f 100644 --- a/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java +++ b/src/test/java/org/opensearch/security/securityconf/impl/v6/ConfigV6Test.java @@ -20,6 +20,10 @@ import org.opensearch.security.DefaultObjectMapper; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + @RunWith(Parameterized.class) public class ConfigV6Test { private final boolean omitDefaults; @@ -31,6 +35,9 @@ public static Iterable omitDefaults() { public void assertEquals(ConfigV6.Kibana expected, JsonNode node) { Assert.assertEquals(expected.multitenancy_enabled, node.get("multitenancy_enabled").asBoolean()); + assertThat(node.get("sign_in_options").isArray(), is(true)); + assertThat(node.get("sign_in_options").toString(), containsString(expected.sign_in_options.get(0).toString())); + if (expected.server_username == null) { Assert.assertNull(node.get("server_username")); } else { @@ -57,6 +64,7 @@ public void assertEquals(ConfigV6.Kibana expected, JsonNode node) { private void assertEquals(ConfigV6.Kibana expected, ConfigV6.Kibana actual) { Assert.assertEquals(expected.multitenancy_enabled, actual.multitenancy_enabled); + assertThat(expected.sign_in_options, is(actual.sign_in_options)); if (expected.server_username == null) { // null is restored to default instead of null Assert.assertEquals(new ConfigV6.Kibana().server_username, actual.server_username); diff --git a/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java b/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java index 542ce878bd..246247c6d9 100644 --- a/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java +++ b/src/test/java/org/opensearch/security/securityconf/impl/v7/ConfigV7Test.java @@ -20,6 +20,10 @@ import org.opensearch.security.DefaultObjectMapper; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + @RunWith(Parameterized.class) public class ConfigV7Test { private final boolean omitDefaults; @@ -31,6 +35,9 @@ public static Iterable omitDefaults() { public void assertEquals(ConfigV7.Kibana expected, JsonNode node) { Assert.assertEquals(expected.multitenancy_enabled, node.get("multitenancy_enabled").asBoolean()); + assertThat(node.get("sign_in_options").isArray(), is(true)); + assertThat(node.get("sign_in_options").toString(), containsString(expected.sign_in_options.get(0).toString())); + if (expected.server_username == null) { Assert.assertNull(node.get("server_username")); } else { @@ -51,6 +58,7 @@ public void assertEquals(ConfigV7.Kibana expected, JsonNode node) { private void assertEquals(ConfigV7.Kibana expected, ConfigV7.Kibana actual) { Assert.assertEquals(expected.multitenancy_enabled, actual.multitenancy_enabled); + assertThat(expected.sign_in_options, is(actual.sign_in_options)); if (expected.server_username == null) { // null is restored to default instead of null Assert.assertEquals(new ConfigV7.Kibana().server_username, actual.server_username); diff --git a/src/test/resources/restapi/config.yml b/src/test/resources/restapi/config.yml index 2ed865657a..7a7d3d0e98 100644 --- a/src/test/resources/restapi/config.yml +++ b/src/test/resources/restapi/config.yml @@ -21,6 +21,18 @@ config: internalProxies: "192\\.168\\.0\\.10|192\\.168\\.0\\.11" remoteIpHeader: "x-forwarded-for" authc: + openid_auth_domain: + http_enabled: true + transport_enabled: true + order: 4 + http_authenticator: + type: openid + challenge: false + config: {} + authentication_backend: + type: "noop" + config: {} + description: "Migrated from v6" authentication_domain_kerb: http_enabled: false transport_enabled: false diff --git a/src/test/resources/restapi/securityconfig_nondefault.json b/src/test/resources/restapi/securityconfig_nondefault.json index 2482e99674..aae6948b01 100644 --- a/src/test/resources/restapi/securityconfig_nondefault.json +++ b/src/test/resources/restapi/securityconfig_nondefault.json @@ -9,7 +9,8 @@ "private_tenant_enabled" : true, "default_tenant" : "", "server_username" : "kibanaserver", - "index" : ".kibana" + "index" : ".kibana", + "sign_in_options":["BASIC"] }, "http" : { "anonymous_auth_enabled" : false, From 91efc3b3f063624c661967806a42fdc3943a06aa Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:58:59 -0400 Subject: [PATCH 10/19] Updates admin password string only if correct hash is present (#4100) Signed-off-by: Darshit Chanpura --- .../security/tools/democonfig/Installer.java | 5 +- .../SecuritySettingsConfigurer.java | 71 +++++++++------- .../SecuritySettingsConfigurerTests.java | 81 +++++++++++++++++-- 3 files changed, 120 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/opensearch/security/tools/democonfig/Installer.java b/src/main/java/org/opensearch/security/tools/democonfig/Installer.java index 864607a9c6..f1ee81f84e 100644 --- a/src/main/java/org/opensearch/security/tools/democonfig/Installer.java +++ b/src/main/java/org/opensearch/security/tools/democonfig/Installer.java @@ -14,6 +14,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -97,7 +98,7 @@ public static Installer getInstance() { * Installs the demo security configuration * @param options the options passed to the script */ - public void installDemoConfiguration(String[] options) { + public void installDemoConfiguration(String[] options) throws IOException { readOptions(options); printScriptHeaders(); gatherUserInputs(); @@ -108,7 +109,7 @@ public void installDemoConfiguration(String[] options) { finishScriptExecution(); } - public static void main(String[] options) { + public static void main(String[] options) throws IOException { Installer installer = Installer.getInstance(); installer.buildOptions(); installer.installDemoConfiguration(options); diff --git a/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java b/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java index 5b497d0f20..92a7915114 100644 --- a/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java +++ b/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java @@ -12,24 +12,22 @@ package org.opensearch.security.tools.democonfig; import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import org.bouncycastle.crypto.generators.OpenBSDBCrypt; import org.opensearch.common.settings.Settings; import org.opensearch.core.common.Strings; -import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.validation.PasswordValidator; import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.support.ConfigConstants; @@ -38,6 +36,7 @@ import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; +import static org.opensearch.security.DefaultObjectMapper.YAML_MAPPER; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX; @@ -81,6 +80,7 @@ public class SecuritySettingsConfigurer { static String ADMIN_USERNAME = "admin"; private final Installer installer; + static final String DEFAULT_ADMIN_PASSWORD = "admin"; public SecuritySettingsConfigurer(Installer installer) { this.installer = installer; @@ -92,7 +92,7 @@ public SecuritySettingsConfigurer(Installer installer) { * 2. Sets the custom admin password (Generates one if none is provided) * 3. Write the security config to opensearch.yml */ - public void configureSecuritySettings() { + public void configureSecuritySettings() throws IOException { checkIfSecurityPluginIsAlreadyConfigured(); updateAdminPassword(); writeSecurityConfigToOpenSearchYML(); @@ -125,9 +125,17 @@ void checkIfSecurityPluginIsAlreadyConfigured() { /** * Replaces the admin password in internal_users.yml with the custom or generated password */ - void updateAdminPassword() { + void updateAdminPassword() throws IOException { String INTERNAL_USERS_FILE_PATH = installer.OPENSEARCH_CONF_DIR + "opensearch-security" + File.separator + "internal_users.yml"; boolean shouldValidatePassword = installer.environment.equals(ExecutionEnvironment.DEMO); + + // check if the password `admin` is present, if not skip updating admin password + if (!isAdminPasswordSetToAdmin(INTERNAL_USERS_FILE_PATH)) { + System.out.println("Admin password seems to be custom configured. Skipping update to admin password."); + return; + } + + // if hashed value for default password "admin" is found, update it with the custom password. try { final PasswordValidator passwordValidator = PasswordValidator.of( Settings.builder() @@ -169,17 +177,29 @@ void updateAdminPassword() { System.exit(-1); } - // Print an update to the logs - System.out.println("Admin password set successfully."); - + // Update the custom password in internal_users.yml file writePasswordToInternalUsersFile(ADMIN_PASSWORD, INTERNAL_USERS_FILE_PATH); + System.out.println("Admin password set successfully."); + } catch (IOException e) { System.out.println("Exception updating the admin password : " + e.getMessage()); System.exit(-1); } } + /** + * Check if the password for admin user was already updated. (Possibly via a custom internal_users.yml) + * @param internalUsersFile Path to internal_users.yml file + * @return true if password was already updated, false otherwise + * @throws IOException if there was an error while reading the file + */ + private boolean isAdminPasswordSetToAdmin(String internalUsersFile) throws IOException { + JsonNode internalUsers = YAML_MAPPER.readTree(new FileInputStream(internalUsersFile)); + return internalUsers.has("admin") + && OpenBSDBCrypt.checkPassword(internalUsers.get("admin").get("hash").asText(), DEFAULT_ADMIN_PASSWORD.toCharArray()); + } + /** * Generate password hash and update it in the internal_users.yml file * @param adminPassword the password to be hashed and updated @@ -190,31 +210,24 @@ void writePasswordToInternalUsersFile(String adminPassword, String internalUsers String hashedAdminPassword = Hasher.hash(adminPassword.toCharArray()); if (hashedAdminPassword.isEmpty()) { - System.out.println("Hash the admin password failure, see console for details"); + System.out.println("Failure while hashing the admin password, see console for details."); System.exit(-1); } - Path tempFilePath = Paths.get(internalUsersFile + ".tmp"); - Path internalUsersPath = Paths.get(internalUsersFile); - - try ( - BufferedReader reader = new BufferedReader(new FileReader(internalUsersFile, StandardCharsets.UTF_8)); - BufferedWriter writer = new BufferedWriter(new FileWriter(tempFilePath.toFile(), StandardCharsets.UTF_8)) - ) { - String line; - while ((line = reader.readLine()) != null) { - if (line.matches(" *hash: *\"\\$2a\\$12\\$VcCDgh2NDk07JGN0rjGbM.Ad41qVR/YFJcgHp0UGns5JDymv..TOG\"")) { - line = line.replace( - "\"$2a$12$VcCDgh2NDk07JGN0rjGbM.Ad41qVR/YFJcgHp0UGns5JDymv..TOG\"", - "\"" + hashedAdminPassword + "\"" - ); - } - writer.write(line + System.lineSeparator()); + try { + var map = YAML_MAPPER.readValue(new File(internalUsersFile), new TypeReference>>() { + }); + var admin = map.get("admin"); + if (admin != null) { + // Replace the password since the default password was found via the check: isAdminPasswordSetToAdmin(..) + admin.put("hash", hashedAdminPassword); } + + // Write the updated map back to the internal_users.yml file + YAML_MAPPER.writeValue(new File(internalUsersFile), map); } catch (IOException e) { throw new IOException("Unable to update the internal users file with the hashed password."); } - Files.move(tempFilePath, internalUsersPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); } /** @@ -329,7 +342,7 @@ static boolean isNodeMaxLocalStorageNodesAlreadyPresent(String filePath) { static boolean isKeyPresentInYMLFile(String filePath, String key) throws IOException { JsonNode node; try { - node = DefaultObjectMapper.YAML_MAPPER.readTree(new File(filePath)); + node = YAML_MAPPER.readTree(new File(filePath)); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/test/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurerTests.java b/src/test/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurerTests.java index 50a65e7fa2..f4b56e6f76 100644 --- a/src/test/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurerTests.java +++ b/src/test/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurerTests.java @@ -21,16 +21,22 @@ import java.io.PrintStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.tools.Hasher; import org.opensearch.security.tools.democonfig.util.NoExitSecurityManager; import static org.hamcrest.MatcherAssert.assertThat; @@ -39,6 +45,7 @@ import static org.hamcrest.Matchers.is; import static org.opensearch.security.dlic.rest.validation.RequestContentValidator.ValidationError.INVALID_PASSWORD_INVALID_REGEX; import static org.opensearch.security.dlic.rest.validation.RequestContentValidator.ValidationError.INVALID_PASSWORD_TOO_SHORT; +import static org.opensearch.security.tools.democonfig.SecuritySettingsConfigurer.DEFAULT_ADMIN_PASSWORD; import static org.opensearch.security.tools.democonfig.SecuritySettingsConfigurer.DEFAULT_PASSWORD_MIN_LENGTH; import static org.opensearch.security.tools.democonfig.SecuritySettingsConfigurer.REST_ENABLED_ROLES; import static org.opensearch.security.tools.democonfig.SecuritySettingsConfigurer.SYSTEM_INDICES; @@ -66,13 +73,14 @@ public class SecuritySettingsConfigurerTests { private static Installer installer; @Before - public void setUp() { + public void setUp() throws IOException { System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(outContent)); installer = Installer.getInstance(); installer.buildOptions(); securitySettingsConfigurer = new SecuritySettingsConfigurer(installer); setUpConf(); + setUpInternalUsersYML(); } @After @@ -87,7 +95,7 @@ public void tearDown() throws NoSuchFieldException, IllegalAccessException { } @Test - public void testUpdateAdminPasswordWithCustomPassword() throws NoSuchFieldException, IllegalAccessException { + public void testUpdateAdminPasswordWithCustomPassword() throws NoSuchFieldException, IllegalAccessException, IOException { String customPassword = "myStrongPassword123"; setEnv(adminPasswordKey, customPassword); @@ -104,7 +112,7 @@ public void testUpdateAdminPassword_noPasswordSupplied() { try { System.setSecurityManager(new NoExitSecurityManager()); securitySettingsConfigurer.updateAdminPassword(); - } catch (SecurityException e) { + } catch (SecurityException | IOException e) { assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); } finally { System.setSecurityManager(null); @@ -125,7 +133,7 @@ public void testUpdateAdminPasswordWithWeakPassword() throws NoSuchFieldExceptio try { System.setSecurityManager(new NoExitSecurityManager()); securitySettingsConfigurer.updateAdminPassword(); - } catch (SecurityException e) { + } catch (SecurityException | IOException e) { assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); } finally { System.setSecurityManager(null); @@ -148,7 +156,7 @@ public void testUpdateAdminPasswordWithShortPassword() throws NoSuchFieldExcepti try { System.setSecurityManager(new NoExitSecurityManager()); securitySettingsConfigurer.updateAdminPassword(); - } catch (SecurityException e) { + } catch (SecurityException | IOException e) { assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); } finally { System.setSecurityManager(null); @@ -160,7 +168,8 @@ public void testUpdateAdminPasswordWithShortPassword() throws NoSuchFieldExcepti } @Test - public void testUpdateAdminPasswordWithWeakPassword_skipPasswordValidation() throws NoSuchFieldException, IllegalAccessException { + public void testUpdateAdminPasswordWithWeakPassword_skipPasswordValidation() throws NoSuchFieldException, IllegalAccessException, + IOException { setEnv(adminPasswordKey, "weakpassword"); installer.environment = ExecutionEnvironment.TEST; securitySettingsConfigurer.updateAdminPassword(); @@ -170,6 +179,49 @@ public void testUpdateAdminPasswordWithWeakPassword_skipPasswordValidation() thr verifyStdOutContainsString("Admin password set successfully."); } + @Test + public void testUpdateAdminPasswordWithCustomInternalUsersYML() throws IOException { + String internalUsersFile = installer.OPENSEARCH_CONF_DIR + "opensearch-security" + File.separator + "internal_users.yml"; + Path internalUsersFilePath = Paths.get(internalUsersFile); + + List newContent = Arrays.asList( + "_meta:", + " type: \"internalusers\"", + " config_version: 2", + "admin:", + " hash: " + Hasher.hash(RandomStringUtils.randomAlphanumeric(16).toCharArray()), + " backend_roles:", + " - \"admin\"" + ); + // overwriting existing content + Files.write(internalUsersFilePath, newContent, StandardCharsets.UTF_8); + + securitySettingsConfigurer.updateAdminPassword(); + + verifyStdOutContainsString("Admin password seems to be custom configured. Skipping update to admin password."); + } + + @Test + public void testUpdateAdminPasswordWithDefaultInternalUsersYml() { + + SecuritySettingsConfigurer.ADMIN_PASSWORD = ""; // to ensure 0 flaky-ness + try { + System.setSecurityManager(new NoExitSecurityManager()); + securitySettingsConfigurer.updateAdminPassword(); + } catch (SecurityException | IOException e) { + assertThat(e.getMessage(), equalTo("System.exit(-1) blocked to allow print statement testing.")); + } finally { + System.setSecurityManager(null); + } + + verifyStdOutContainsString( + String.format( + "No custom admin password found. Please provide a password via the environment variable %s.", + ConfigConstants.OPENSEARCH_INITIAL_ADMIN_PASSWORD + ) + ); + } + @Test public void testSecurityPluginAlreadyConfigured() { securitySettingsConfigurer.writeSecurityConfigToOpenSearchYML(); @@ -353,4 +405,21 @@ void setUpConf() { private void verifyStdOutContainsString(String s) { assertThat(outContent.toString(), containsString(s)); } + + private void setUpInternalUsersYML() throws IOException { + String internalUsersFile = installer.OPENSEARCH_CONF_DIR + "opensearch-security" + File.separator + "internal_users.yml"; + Path internalUsersFilePath = Paths.get(internalUsersFile); + List defaultContent = Arrays.asList( + "_meta:", + " type: \"internalusers\"", + " config_version: 2", + "admin:", + " hash: " + Hasher.hash(DEFAULT_ADMIN_PASSWORD.toCharArray()), + " reserved: " + true, + " backend_roles:", + " - \"admin\"", + " description: Demo admin user" + ); + Files.write(internalUsersFilePath, defaultContent, StandardCharsets.UTF_8); + } } From 633ff9b30cd34acfb81a264fc49b3d716dd89c64 Mon Sep 17 00:00:00 2001 From: Sicheng Song Date: Tue, 19 Mar 2024 16:10:55 -0700 Subject: [PATCH 11/19] Add query assistant role and new ml system indices (#4141) Signed-off-by: Sicheng Song --- config/roles.yml | 9 +++++++++ .../tools/democonfig/SecuritySettingsConfigurer.java | 2 ++ 2 files changed, 11 insertions(+) diff --git a/config/roles.yml b/config/roles.yml index efa83ed02e..e90a4e62a6 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -271,6 +271,15 @@ cross_cluster_search_remote_full_access: - 'indices:admin/shards/search_shards' - 'indices:data/read/search' +# Allow users to operate query assistant +ml_query_assistant_access: + reserved: true + cluster_permissions: + - 'cluster:admin/opensearch/ml/execute' + - 'cluster:admin/opensearch/ml/memory/conversation/create' + - 'cluster:admin/opensearch/ml/memory/interaction/create' + - 'cluster:admin/opensearch/ml/predict' + # Allow users to read ML stats/models/tasks ml_read_access: reserved: true diff --git a/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java b/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java index 92a7915114..e66215ac9a 100644 --- a/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java +++ b/src/main/java/org/opensearch/security/tools/democonfig/SecuritySettingsConfigurer.java @@ -47,8 +47,10 @@ public class SecuritySettingsConfigurer { static final List REST_ENABLED_ROLES = List.of("all_access", "security_rest_api_access"); static final List SYSTEM_INDICES = List.of( + ".plugins-ml-agent", ".plugins-ml-config", ".plugins-ml-connector", + ".plugins-ml-controller", ".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", From fa877babe3dac13c30bcc2bcbe4d484bcdb6101f Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Wed, 20 Mar 2024 12:05:06 -0500 Subject: [PATCH 12/19] [Feature] Check for and perform upgrades on security configurations (#4102) ### Description This adds a new API that allows for checking and updating configurations from the default configurations on disk. Initial feature supports only Roles. #### Response when no upgrade is available ``` GET _plugins/_security/api/_upgrade_check 200 { "status": "ok", "upgradeAvailable" : false } ``` #### Response when a new role is available ``` GET _plugins/_security/api/_upgrade_check 200 { "status": "ok", "upgradeAvailable" : true, "upgradeActions" : { "roles" : { "add" : [ "flow_framework_full_access" ] } } } ``` #### Response when a new role + existing role were updated ``` GET _plugins/_security/api/_upgrade_check 200 { "status": "ok", "upgradeAvailable" : true, "upgradeActions" : { "roles" : { "add" : [ "flow_framework_full_access" ], "modify" : [ "flow_framework_read_access" ] } } } ``` #### Perform an upgrade ``` POST _plugins/_security/api/_upgrade_perform 200 { "status" : "OK", "upgrades" : { "roles" : { "add" : [ "flow_framework_full_access" ], "modify" : [ "flow_framework_read_access" ] } } } ``` #### Perform an upgrade when unneeded ``` POST _plugins/_security/api/_upgrade_perform 400 { "status": "BAD_REQUEST", "message": "Unable to upgrade, no differences found in 'roles' config" } ``` ### Issues Resolved - https://github.com/opensearch-project/security/issues/2316 ### Testing New unit test and integration test cases ### Check List - [X] New functionality includes testing - [ ] New functionality has been documented - [X] Commits are signed per the DCO using --signoff By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). --------- Signed-off-by: Peter Nied Signed-off-by: Andrey Pleskach Signed-off-by: Peter Nied --- .../security/DefaultConfigurationTests.java | 100 ++++- src/integrationTest/resources/roles.yml | 18 + .../ConfigurationRepository.java | 13 +- .../dlic/rest/api/AbstractApiAction.java | 54 ++- .../dlic/rest/api/ConfigUpgradeApiAction.java | 400 ++++++++++++++++++ .../dlic/rest/api/SecurityRestApiActions.java | 3 +- .../validation/RequestContentValidator.java | 4 +- .../rest/validation/ValidationResult.java | 19 +- .../security/securityconf/impl/CType.java | 59 ++- .../api/AbstractApiActionValidationTest.java | 4 +- .../api/ConfigUpgradeApiActionUnitTest.java | 291 +++++++++++++ .../InternalUsersApiActionValidationTest.java | 20 +- 12 files changed, 929 insertions(+), 56 deletions(-) create mode 100644 src/main/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiAction.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiActionUnitTest.java diff --git a/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java b/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java index 8bb5b96145..eb028c74e4 100644 --- a/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java +++ b/src/integrationTest/java/org/opensearch/security/DefaultConfigurationTests.java @@ -11,10 +11,13 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.io.FileUtils; import org.awaitility.Awaitility; import org.junit.AfterClass; @@ -22,6 +25,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.opensearch.test.framework.TestSecurityConfig.User; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; @@ -32,16 +36,16 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class DefaultConfigurationTests { private final static Path configurationFolder = ConfigurationFiles.createConfigurationDirectory(); - public static final String ADMIN_USER_NAME = "admin"; - public static final String DEFAULT_PASSWORD = "secret"; - public static final String NEW_USER = "new-user"; - public static final String LIMITED_USER = "limited-user"; + private static final User ADMIN_USER = new User("admin"); + private static final User NEW_USER = new User("new-user"); + private static final User LIMITED_USER = new User("limited-user"); @ClassRule public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) @@ -64,15 +68,95 @@ public static void cleanConfigurationDirectory() throws IOException { @Test public void shouldLoadDefaultConfiguration() { - try (TestRestClient client = cluster.getRestClient(NEW_USER, DEFAULT_PASSWORD)) { + try (TestRestClient client = cluster.getRestClient(NEW_USER)) { Awaitility.await().alias("Load default configuration").until(() -> client.getAuthInfo().getStatusCode(), equalTo(200)); } - try (TestRestClient client = cluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) { - client.confirmCorrectCredentials(ADMIN_USER_NAME); + try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { + client.confirmCorrectCredentials(ADMIN_USER.getName()); HttpResponse response = client.get("_plugins/_security/api/internalusers"); response.assertStatusCode(200); Map users = response.getBodyAs(Map.class); - assertThat(users, allOf(aMapWithSize(3), hasKey(ADMIN_USER_NAME), hasKey(NEW_USER), hasKey(LIMITED_USER))); + assertThat( + users, + allOf(aMapWithSize(3), hasKey(ADMIN_USER.getName()), hasKey(NEW_USER.getName()), hasKey(LIMITED_USER.getName())) + ); } } + + @Test + public void securityRolesUgrade() throws Exception { + try (var client = cluster.getRestClient(ADMIN_USER)) { + // Setup: Make sure the config is ready before starting modifications + Awaitility.await().alias("Load default configuration").until(() -> client.getAuthInfo().getStatusCode(), equalTo(200)); + + // Setup: Collect default roles after cluster start + final var expectedRoles = client.get("_plugins/_security/api/roles/"); + final var expectedRoleNames = extractFieldNames(expectedRoles.getBodyAs(JsonNode.class)); + + // Verify: Before any changes, nothing to upgrade + final var upgradeCheck = client.get("_plugins/_security/api/_upgrade_check"); + upgradeCheck.assertStatusCode(200); + assertThat(upgradeCheck.getBooleanFromJsonBody("/upgradeAvailable"), equalTo(false)); + + // Action: Select a role that is part of the defaults and delete that role + final var roleToDelete = "flow_framework_full_access"; + client.delete("_plugins/_security/api/roles/" + roleToDelete).assertStatusCode(200); + + // Action: Select a role that is part of the defaults and alter that role with removal, edits, and additions + final var roleToAlter = "flow_framework_read_access"; + final var originalRoleConfig = client.get("_plugins/_security/api/roles/" + roleToAlter).getBodyAs(JsonNode.class); + final var alteredRoleReponse = client.patch("_plugins/_security/api/roles/" + roleToAlter, "[\n" + // + " {\n" + // + " \"op\": \"replace\",\n" + // + " \"path\": \"/cluster_permissions\",\n" + // + " \"value\": [\"a\", \"b\", \"c\"]\n" + // + " },\n" + // + " {\n" + // + " \"op\": \"add\",\n" + // + " \"path\": \"/index_permissions\",\n" + // + " \"value\": [ {\n" + // + " \"index_patterns\": [\"*\"],\n" + // + " \"allowed_actions\": [\"*\"]\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + "]"); + alteredRoleReponse.assertStatusCode(200); + final var alteredRoleJson = alteredRoleReponse.getBodyAs(JsonNode.class); + assertThat(originalRoleConfig, not(equalTo(alteredRoleJson))); + + // Verify: Confirm that the upgrade check detects the changes associated with both role resources + final var upgradeCheckAfterChanges = client.get("_plugins/_security/api/_upgrade_check"); + upgradeCheckAfterChanges.assertStatusCode(200); + assertThat( + upgradeCheckAfterChanges.getTextArrayFromJsonBody("/upgradeActions/roles/add"), + equalTo(List.of("flow_framework_full_access")) + ); + assertThat( + upgradeCheckAfterChanges.getTextArrayFromJsonBody("/upgradeActions/roles/modify"), + equalTo(List.of("flow_framework_read_access")) + ); + + // Action: Perform the upgrade to the roles configuration + final var performUpgrade = client.post("_plugins/_security/api/_upgrade_perform"); + performUpgrade.assertStatusCode(200); + assertThat(performUpgrade.getTextArrayFromJsonBody("/upgrades/roles/add"), equalTo(List.of("flow_framework_full_access"))); + assertThat(performUpgrade.getTextArrayFromJsonBody("/upgrades/roles/modify"), equalTo(List.of("flow_framework_read_access"))); + + // Verify: Same roles as the original state - the deleted role has been restored + final var afterUpgradeRoles = client.get("_plugins/_security/api/roles/"); + final var afterUpgradeRolesNames = extractFieldNames(afterUpgradeRoles.getBodyAs(JsonNode.class)); + assertThat(afterUpgradeRolesNames, equalTo(expectedRoleNames)); + + // Verify: Altered role was restored to its expected state + final var afterUpgradeAlteredRoleConfig = client.get("_plugins/_security/api/roles/" + roleToAlter).getBodyAs(JsonNode.class); + assertThat(originalRoleConfig, equalTo(afterUpgradeAlteredRoleConfig)); + } + } + + private Set extractFieldNames(final JsonNode json) { + final var set = new HashSet(); + json.fieldNames().forEachRemaining(set::add); + return set; + } } diff --git a/src/integrationTest/resources/roles.yml b/src/integrationTest/resources/roles.yml index 02de9bf3d5..2ea7548ad6 100644 --- a/src/integrationTest/resources/roles.yml +++ b/src/integrationTest/resources/roles.yml @@ -17,3 +17,21 @@ user_limited-user__limited-role: allowed_actions: - "indices:data/read/get" - "indices:data/read/search" +flow_framework_full_access: + cluster_permissions: + - 'cluster:admin/opensearch/flow_framework/*' + - 'cluster_monitor' + index_permissions: + - index_patterns: + - '*' + allowed_actions: + - 'indices:admin/aliases/get' + - 'indices:admin/mappings/get' + - 'indices_monitor' +flow_framework_read_access: + cluster_permissions: + - 'cluster:admin/opensearch/flow_framework/workflow/get' + - 'cluster:admin/opensearch/flow_framework/workflow/search' + - 'cluster:admin/opensearch/flow_framework/workflow_state/get' + - 'cluster:admin/opensearch/flow_framework/workflow_state/search' + - 'cluster:admin/opensearch/flow_framework/workflow_step/get' diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index dfbeb16cb3..353286fc4a 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -123,6 +123,14 @@ private ConfigurationRepository( configCache = CacheBuilder.newBuilder().build(); } + public String getConfigDirectory() { + String lookupDir = System.getProperty("security.default_init.dir"); + final String cd = lookupDir != null + ? (lookupDir + "/") + : new Environment(settings, configPath).configDir().toAbsolutePath().toString() + "/opensearch-security/"; + return cd; + } + private void initalizeClusterConfiguration(final boolean installDefaultConfig) { try { LOGGER.info("Background init thread started. Install default config?: " + installDefaultConfig); @@ -135,10 +143,7 @@ private void initalizeClusterConfiguration(final boolean installDefaultConfig) { if (installDefaultConfig) { try { - String lookupDir = System.getProperty("security.default_init.dir"); - final String cd = lookupDir != null - ? (lookupDir + "/") - : new Environment(settings, configPath).configDir().toAbsolutePath().toString() + "/opensearch-security/"; + final String cd = getConfigDirectory(); File confFile = new File(cd + "config.yml"); if (confFile.exists()) { final ThreadContext threadContext = threadPool.getThreadContext(); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java index ef8a00d700..a38d618da0 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java @@ -35,13 +35,16 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.CheckedSupplier; +import org.opensearch.common.action.ActionFuture; import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; -import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.Strings; +import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentHelper; import org.opensearch.index.engine.VersionConflictEngineException; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; @@ -226,7 +229,7 @@ protected final ValidationResult patchEntity( ); } - protected final ValidationResult patchEntities( + protected ValidationResult patchEntities( final RestRequest request, final JsonNode patchContent, final SecurityConfiguration securityConfiguration @@ -336,7 +339,7 @@ final void saveOrUpdateConfiguration( final SecurityDynamicConfiguration configuration, final OnSucessActionListener onSucessActionListener ) { - saveAndUpdateConfigs(securityApiDependencies.securityIndexName(), client, getConfigType(), configuration, onSucessActionListener); + saveAndUpdateConfigsAsync(securityApiDependencies, client, getConfigType(), configuration, onSucessActionListener); } protected final String nameParam(final RestRequest request) { @@ -367,7 +370,7 @@ protected final ValidationResult loadConfiguration(final ); } - protected final ValidationResult> loadConfiguration( + protected ValidationResult> loadConfiguration( final CType cType, boolean omitSensitiveData, final boolean logComplianceEvent @@ -485,30 +488,45 @@ public final void onFailure(Exception e) { } - public static void saveAndUpdateConfigs( - final String indexName, + public static ActionFuture saveAndUpdateConfigs( + final SecurityApiDependencies dependencies, + final Client client, + final CType cType, + final SecurityDynamicConfiguration configuration + ) { + final var request = createIndexRequestForConfig(dependencies, cType, configuration); + return client.index(request); + } + + public static void saveAndUpdateConfigsAsync( + final SecurityApiDependencies dependencies, final Client client, final CType cType, final SecurityDynamicConfiguration configuration, final ActionListener actionListener ) { - final IndexRequest ir = new IndexRequest(indexName); - final String id = cType.toLCString(); + final var ir = createIndexRequestForConfig(dependencies, cType, configuration); + client.index(ir, new ConfigUpdatingActionListener<>(new String[] { cType.toLCString() }, client, actionListener)); + } + private static IndexRequest createIndexRequestForConfig( + final SecurityApiDependencies dependencies, + final CType cType, + final SecurityDynamicConfiguration configuration + ) { configuration.removeStatic(); - + final BytesReference content; try { - client.index( - ir.id(id) - .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .setIfSeqNo(configuration.getSeqNo()) - .setIfPrimaryTerm(configuration.getPrimaryTerm()) - .source(id, XContentHelper.toXContent(configuration, XContentType.JSON, false)), - new ConfigUpdatingActionListener<>(new String[] { id }, client, actionListener) - ); - } catch (IOException e) { + content = XContentHelper.toXContent(configuration, XContentType.JSON, ToXContent.EMPTY_PARAMS, false); + } catch (final IOException e) { throw ExceptionsHelper.convertToOpenSearchException(e); } + + return new IndexRequest(dependencies.securityIndexName()).id(cType.toLCString()) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .setIfSeqNo(configuration.getSeqNo()) + .setIfPrimaryTerm(configuration.getPrimaryTerm()) + .source(cType.toLCString(), content); } protected static class ConfigUpdatingActionListener implements ActionListener { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiAction.java new file mode 100644 index 0000000000..f295ab8c1c --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiAction.java @@ -0,0 +1,400 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.collect.Tuple; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.RestRequest.Method; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.dlic.rest.support.Utils; +import org.opensearch.security.dlic.rest.validation.EndpointValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; +import org.opensearch.security.dlic.rest.validation.ValidationResult; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.support.ConfigHelper; +import org.opensearch.threadpool.ThreadPool; + +import com.flipkart.zjsonpatch.DiffFlags; +import com.flipkart.zjsonpatch.JsonDiff; + +import static org.opensearch.security.dlic.rest.api.Responses.badRequestMessage; +import static org.opensearch.security.dlic.rest.api.Responses.response; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; +import static org.opensearch.security.dlic.rest.support.Utils.withIOException; + +public class ConfigUpgradeApiAction extends AbstractApiAction { + + private final static Logger LOGGER = LogManager.getLogger(ConfigUpgradeApiAction.class); + + private final static Set SUPPORTED_CTYPES = ImmutableSet.of(CType.ROLES); + + private final static String REQUEST_PARAM_CONFIGS_KEY = "configs"; + + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(Method.GET, "/_upgrade_check"), new Route(Method.POST, "/_upgrade_perform")) + ); + + @Inject + public ConfigUpgradeApiAction( + final ClusterService clusterService, + final ThreadPool threadPool, + final SecurityApiDependencies securityApiDependencies + ) { + super(Endpoint.CONFIG, clusterService, threadPool, securityApiDependencies); + this.requestHandlersBuilder.configureRequestHandlers(rhb -> { + rhb.allMethodsNotImplemented().add(Method.GET, this::canUpgrade).add(Method.POST, this::performUpgrade); + }); + } + + void canUpgrade(final RestChannel channel, final RestRequest request, final Client client) throws IOException { + getAndValidateConfigurationsToUpgrade(request).map(this::configurationDifferences).valid(differencesList -> { + final var allConfigItemChanges = differencesList.stream() + .map(kvp -> new ConfigItemChanges(kvp.v1(), kvp.v2())) + .collect(Collectors.toList()); + + final var upgradeAvailable = allConfigItemChanges.stream().anyMatch(ConfigItemChanges::hasChanges); + + final ObjectNode response = JsonNodeFactory.instance.objectNode(); + response.put("status", "OK"); + response.put("upgradeAvailable", upgradeAvailable); + + if (upgradeAvailable) { + final ObjectNode differences = JsonNodeFactory.instance.objectNode(); + allConfigItemChanges.forEach(configItemChanges -> configItemChanges.addToNode(differences)); + response.set("upgradeActions", differences); + } + channel.sendResponse(new BytesRestResponse(RestStatus.OK, XContentType.JSON.mediaType(), response.toPrettyString())); + }).error((status, toXContent) -> response(channel, status, toXContent)); + } + + void performUpgrade(final RestChannel channel, final RestRequest request, final Client client) throws IOException { + getAndValidateConfigurationsToUpgrade(request).map(this::configurationDifferences) + .map(this::verifyHasDifferences) + .map(diffs -> applyDifferences(request, client, diffs)) + .valid(updatedConfigs -> { + final var response = JsonNodeFactory.instance.objectNode(); + response.put("status", "OK"); + + final var allUpdates = JsonNodeFactory.instance.objectNode(); + updatedConfigs.forEach(configItemChanges -> configItemChanges.addToNode(allUpdates)); + response.set("upgrades", allUpdates); + + channel.sendResponse(new BytesRestResponse(RestStatus.OK, XContentType.JSON.mediaType(), response.toPrettyString())); + }) + .error((status, toXContent) -> response(channel, status, toXContent)); + } + + private ValidationResult> applyDifferences( + final RestRequest request, + final Client client, + final List> differencesToUpdate + ) { + try { + final var updatedResources = new ArrayList>(); + for (final Tuple difference : differencesToUpdate) { + updatedResources.add( + loadConfiguration(difference.v1(), false, false).map( + configuration -> patchEntities(request, difference.v2(), SecurityConfiguration.of(null, configuration)).map( + patchResults -> { + final var response = saveAndUpdateConfigs( + securityApiDependencies, + client, + difference.v1(), + patchResults.configuration() + ); + return ValidationResult.success(response.actionGet()); + } + ).map(indexResponse -> { + + final var itemsGroupedByOperation = new ConfigItemChanges(difference.v1(), difference.v2()); + return ValidationResult.success(itemsGroupedByOperation); + }) + ) + ); + } + + return ValidationResult.merge(updatedResources); + } catch (final Exception ioe) { + LOGGER.debug("Error while applying differences", ioe); + return ValidationResult.error( + RestStatus.BAD_REQUEST, + badRequestMessage("Error applying configuration, see the log file to troubleshoot.") + ); + } + + } + + ValidationResult>> verifyHasDifferences(List> diffs) { + if (diffs.isEmpty()) { + return ValidationResult.error(RestStatus.BAD_REQUEST, badRequestMessage("Unable to upgrade, no differences found")); + } + + for (final var diff : diffs) { + if (diff.v2().size() == 0) { + return ValidationResult.error( + RestStatus.BAD_REQUEST, + badRequestMessage("Unable to upgrade, no differences found in '" + diff.v1().toLCString() + "' config") + ); + } + } + return ValidationResult.success(diffs); + } + + private ValidationResult>> configurationDifferences(final Set configurations) { + try { + final var differences = new ArrayList>>(); + for (final var configuration : configurations) { + differences.add(computeDifferenceToUpdate(configuration)); + } + return ValidationResult.merge(differences); + } catch (final UncheckedIOException ioe) { + LOGGER.error("Error while processing differences", ioe.getCause()); + return ValidationResult.error( + RestStatus.BAD_REQUEST, + badRequestMessage("Error processing configuration, see the log file to troubleshoot.") + ); + } + } + + ValidationResult> computeDifferenceToUpdate(final CType configType) { + return withIOException(() -> loadConfiguration(configType, false, false).map(activeRoles -> { + final var activeRolesJson = Utils.convertJsonToJackson(activeRoles, true); + final var defaultRolesJson = loadConfigFileAsJson(configType); + final var rawDiff = JsonDiff.asJson(activeRolesJson, defaultRolesJson, EnumSet.of(DiffFlags.OMIT_VALUE_ON_REMOVE)); + return ValidationResult.success(new Tuple<>(configType, filterRemoveOperations(rawDiff))); + })); + } + + private ValidationResult> getAndValidateConfigurationsToUpgrade(final RestRequest request) { + final String[] configs = request.paramAsStringArray(REQUEST_PARAM_CONFIGS_KEY, null); + + final Set configurations; + try { + configurations = Optional.ofNullable(configs).map(CType::fromStringValues).orElse(SUPPORTED_CTYPES); + } catch (final IllegalArgumentException iae) { + return ValidationResult.error( + RestStatus.BAD_REQUEST, + badRequestMessage("Found invalid configuration option, valid options are: " + CType.lcStringValues()) + ); + } + + if (!configurations.stream().allMatch(SUPPORTED_CTYPES::contains)) { + // Remove all supported configurations + configurations.removeAll(SUPPORTED_CTYPES); + return ValidationResult.error( + RestStatus.BAD_REQUEST, + badRequestMessage("Unsupported configurations for upgrade, " + configurations) + ); + } + + return ValidationResult.success(configurations); + } + + private JsonNode filterRemoveOperations(final JsonNode diff) { + final ArrayNode filteredDiff = JsonNodeFactory.instance.arrayNode(); + diff.forEach(node -> { + if (!isRemoveOperation(node)) { + filteredDiff.add(node); + return; + } else { + if (!hasRootLevelPath(node)) { + filteredDiff.add(node); + } + } + }); + return filteredDiff; + } + + private static String pathRoot(final JsonNode node) { + return node.get("path").asText().split("/")[1]; + } + + private static boolean hasRootLevelPath(final JsonNode node) { + final var jsonPath = node.get("path").asText(); + return jsonPath.charAt(0) == '/' && !jsonPath.substring(1).contains("/"); + } + + private static boolean isRemoveOperation(final JsonNode node) { + return node.get("op").asText().equals("remove"); + } + + private SecurityDynamicConfiguration loadYamlFile(final String filepath, final CType cType) throws IOException { + return ConfigHelper.fromYamlFile(filepath, cType, ConfigurationRepository.DEFAULT_CONFIG_VERSION, 0, 0); + } + + JsonNode loadConfigFileAsJson(final CType cType) throws IOException { + final var cd = securityApiDependencies.configurationRepository().getConfigDirectory(); + final var filepath = cType.configFile(Path.of(cd)).toString(); + try { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + final var loadedConfiguration = loadYamlFile(filepath, cType); + return Utils.convertJsonToJackson(loadedConfiguration, true); + }); + } catch (final PrivilegedActionException e) { + LOGGER.error("Error when loading configuration from file", e); + throw (IOException) e.getCause(); + } + } + + @Override + public List routes() { + return routes; + } + + @Override + protected CType getConfigType() { + throw new UnsupportedOperationException("This class supports multiple configuration types"); + } + + @Override + protected EndpointValidator createEndpointValidator() { + return new EndpointValidator() { + + @Override + public Endpoint endpoint() { + return endpoint; + } + + @Override + public RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator() { + return securityApiDependencies.restApiAdminPrivilegesEvaluator(); + } + + @Override + public ValidationResult entityReserved(SecurityConfiguration securityConfiguration) { + // Allow modification of reserved entities + return ValidationResult.success(securityConfiguration); + } + + @Override + public ValidationResult entityHidden(SecurityConfiguration securityConfiguration) { + // Allow modification of hidden entities + return ValidationResult.success(securityConfiguration); + } + + @Override + public RequestContentValidator createRequestContentValidator(final Object... params) { + return new ConfigUpgradeContentValidator(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return securityApiDependencies.settings(); + } + + @Override + public Map allowedKeys() { + return Map.of(REQUEST_PARAM_CONFIGS_KEY, DataType.ARRAY); + } + }); + } + }; + } + + /** More permissions validation that default ContentValidator */ + static class ConfigUpgradeContentValidator extends RequestContentValidator { + + protected ConfigUpgradeContentValidator(final ValidationContext validationContext) { + super(validationContext); + } + + @Override + public ValidationResult validate(final RestRequest request, final JsonNode jsonContent) throws IOException { + return validateContentSize(jsonContent); + } + } + + /** Tranforms config changes from a raw PATCH into simplier view */ + static class ConfigItemChanges { + + private final CType config; + private final Map> itemsGroupedByOperation; + + public ConfigItemChanges(final CType config, final JsonNode differences) { + this.config = config; + this.itemsGroupedByOperation = classifyChanges(differences); + } + + public boolean hasChanges() { + return !itemsGroupedByOperation.isEmpty(); + } + + /** Adds the config item changes to the json node */ + public void addToNode(final ObjectNode node) { + final var allOperations = JsonNodeFactory.instance.objectNode(); + itemsGroupedByOperation.forEach((operation, items) -> { + final var arrayNode = allOperations.putArray(operation); + items.forEach(arrayNode::add); + }); + node.set(config.toLCString(), allOperations); + } + + /** + * Classifies the changes to this config into groupings by the type of change, for + * multiple changes types on the same item they are groupped as 'modify' + */ + private static Map> classifyChanges(final JsonNode differences) { + final var items = new HashMap(); + differences.forEach(node -> { + final var item = pathRoot(node); + final var operation = node.get("op").asText(); + if (items.containsKey(item) && !items.get(item).equals(operation)) { + items.put(item, "modify"); + } else { + items.put(item, operation); + } + }); + + final var itemsGroupedByOperation = items.entrySet() + .stream() + .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); + return itemsGroupedByOperation; + } + } +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java index b0d46f8774..f38cf0580d 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java @@ -95,7 +95,8 @@ public static Collection getHandler( new AllowlistApiAction(Endpoint.ALLOWLIST, clusterService, threadPool, securityApiDependencies), new AuditApiAction(clusterService, threadPool, securityApiDependencies), new MultiTenancyConfigApiAction(clusterService, threadPool, securityApiDependencies), - new SecuritySSLCertsApiAction(clusterService, threadPool, securityKeyStore, certificatesReloadEnabled, securityApiDependencies) + new SecuritySSLCertsApiAction(clusterService, threadPool, securityKeyStore, certificatesReloadEnabled, securityApiDependencies), + new ConfigUpgradeApiAction(clusterService, threadPool, securityApiDependencies) ); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java index 452bdd72e4..4d9faf096c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java @@ -142,7 +142,7 @@ private ValidationResult parseRequestContent(final RestRequest request } } - private ValidationResult validateContentSize(final JsonNode jsonContent) { + protected ValidationResult validateContentSize(final JsonNode jsonContent) { if (jsonContent.isEmpty()) { this.validationError = ValidationError.PAYLOAD_MANDATORY; return ValidationResult.error(RestStatus.BAD_REQUEST, this); @@ -150,7 +150,7 @@ private ValidationResult validateContentSize(final JsonNode jsonConten return ValidationResult.success(jsonContent); } - private ValidationResult validateJsonKeys(final JsonNode jsonContent) { + protected ValidationResult validateJsonKeys(final JsonNode jsonContent) { final Set requestedKeys = new HashSet<>(); jsonContent.fieldNames().forEachRemaining(requestedKeys::add); // mandatory settings, one of ... diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java b/src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java index ea782ea504..921ed4675d 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java @@ -12,7 +12,9 @@ package org.opensearch.security.dlic.rest.validation; import java.io.IOException; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import org.opensearch.common.CheckedBiConsumer; import org.opensearch.common.CheckedConsumer; @@ -50,6 +52,22 @@ public static ValidationResult error(final RestStatus status, final ToXCo return new ValidationResult<>(status, errorMessage); } + /** + * Transforms a list of validation results into a single validation result of that lists contents. + * If any of the validation results are not valid, the first is returned as the error. + */ + public static ValidationResult> merge(final List> results) { + if (results.stream().allMatch(ValidationResult::isValid)) { + return success(results.stream().map(result -> result.content).collect(Collectors.toList())); + } + + return results.stream() + .filter(result -> !result.isValid()) + .map(failedResult -> new ValidationResult>(failedResult.status, failedResult.errorMessage)) + .findFirst() + .get(); + } + public ValidationResult map(final CheckedFunction, IOException> mapper) throws IOException { if (content != null) { return Objects.requireNonNull(mapper).apply(content); @@ -82,5 +100,4 @@ public boolean isValid() { public ToXContent errorMessage() { return errorMessage; } - } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/CType.java b/src/main/java/org/opensearch/security/securityconf/impl/CType.java index 4e5e2de496..23158e5850 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/CType.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/CType.java @@ -27,14 +27,17 @@ package org.opensearch.security.securityconf.impl; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; +import com.google.common.collect.ImmutableMap; + import org.opensearch.security.auditlog.config.AuditConfig; import org.opensearch.security.securityconf.impl.v6.ActionGroupsV6; import org.opensearch.security.securityconf.impl.v6.ConfigV6; @@ -50,21 +53,39 @@ public enum CType { - INTERNALUSERS(toMap(1, InternalUserV6.class, 2, InternalUserV7.class)), - ACTIONGROUPS(toMap(0, List.class, 1, ActionGroupsV6.class, 2, ActionGroupsV7.class)), - CONFIG(toMap(1, ConfigV6.class, 2, ConfigV7.class)), - ROLES(toMap(1, RoleV6.class, 2, RoleV7.class)), - ROLESMAPPING(toMap(1, RoleMappingsV6.class, 2, RoleMappingsV7.class)), - TENANTS(toMap(2, TenantV7.class)), - NODESDN(toMap(1, NodesDn.class, 2, NodesDn.class)), - WHITELIST(toMap(1, WhitelistingSettings.class, 2, WhitelistingSettings.class)), - ALLOWLIST(toMap(1, AllowlistingSettings.class, 2, AllowlistingSettings.class)), - AUDIT(toMap(1, AuditConfig.class, 2, AuditConfig.class)); + ACTIONGROUPS(toMap(0, List.class, 1, ActionGroupsV6.class, 2, ActionGroupsV7.class), "action_groups.yml", false), + ALLOWLIST(toMap(1, AllowlistingSettings.class, 2, AllowlistingSettings.class), "allowlist.yml", true), + AUDIT(toMap(1, AuditConfig.class, 2, AuditConfig.class), "audit.yml", true), + CONFIG(toMap(1, ConfigV6.class, 2, ConfigV7.class), "config.yml", false), + INTERNALUSERS(toMap(1, InternalUserV6.class, 2, InternalUserV7.class), "internal_users.yml", false), + NODESDN(toMap(1, NodesDn.class, 2, NodesDn.class), "nodes_dn.yml", true), + ROLES(toMap(1, RoleV6.class, 2, RoleV7.class), "roles.yml", false), + ROLESMAPPING(toMap(1, RoleMappingsV6.class, 2, RoleMappingsV7.class), "roles_mapping.yml", false), + TENANTS(toMap(2, TenantV7.class), "tenants.yml", false), + WHITELIST(toMap(1, WhitelistingSettings.class, 2, WhitelistingSettings.class), "whitelist.yml", true); + + public static final List REQUIRED_CONFIG_FILES = Arrays.stream(CType.values()) + .filter(Predicate.not(CType::emptyIfMissing)) + .collect(Collectors.toList()); + + public static final List NOT_REQUIRED_CONFIG_FILES = Arrays.stream(CType.values()) + .filter(CType::emptyIfMissing) + .collect(Collectors.toList()); + + private final Map> implementations; + + private final String configFileName; - private Map> implementations; + private final boolean emptyIfMissing; - private CType(Map> implementations) { + private CType(Map> implementations, final String configFileName, final boolean emptyIfMissing) { this.implementations = implementations; + this.configFileName = configFileName; + this.emptyIfMissing = emptyIfMissing; + } + + public boolean emptyIfMissing() { + return emptyIfMissing; } public Map> getImplementationClass() { @@ -80,18 +101,22 @@ public String toLCString() { } public static Set lcStringValues() { - return Arrays.stream(CType.values()).map(c -> c.toLCString()).collect(Collectors.toSet()); + return Arrays.stream(CType.values()).map(CType::toLCString).collect(Collectors.toSet()); } public static Set fromStringValues(String[] strings) { - return Arrays.stream(strings).map(c -> CType.fromString(c)).collect(Collectors.toSet()); + return Arrays.stream(strings).map(CType::fromString).collect(Collectors.toSet()); + } + + public Path configFile(final Path configDir) { + return configDir.resolve(this.configFileName); } private static Map> toMap(Object... objects) { - final Map> map = new HashMap>(); + final ImmutableMap.Builder> map = ImmutableMap.builder(); for (int i = 0; i < objects.length; i = i + 2) { map.put((Integer) objects[i], (Class) objects[i + 1]); } - return Collections.unmodifiableMap(map); + return map.build(); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractApiActionValidationTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractApiActionValidationTest.java index f2df09549f..4b2e9e4417 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractApiActionValidationTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractApiActionValidationTest.java @@ -116,10 +116,12 @@ protected CType getConfigType() { } - protected JsonNode xContentToJsonNode(final ToXContent toXContent) throws IOException { + protected JsonNode xContentToJsonNode(final ToXContent toXContent) { try (final var xContentBuilder = XContentFactory.jsonBuilder()) { toXContent.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS); return DefaultObjectMapper.readTree(xContentBuilder.toString()); + } catch (final IOException ioe) { + throw new RuntimeException(ioe); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiActionUnitTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiActionUnitTest.java new file mode 100644 index 0000000000..36407cfbc4 --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ConfigUpgradeApiActionUnitTest.java @@ -0,0 +1,291 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.api; + +import java.io.IOException; +import java.util.List; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableList; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Before; +import org.junit.Test; + +import org.opensearch.action.index.IndexResponse; +import org.opensearch.client.Client; +import org.opensearch.common.action.ActionFuture; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.RestResponse; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.dlic.rest.support.Utils; +import org.opensearch.security.dlic.rest.validation.ValidationResult; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; + +import org.mockito.Mock; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +public class ConfigUpgradeApiActionUnitTest extends AbstractApiActionValidationTest { + + @Mock + private Client client; + + @Mock + private RestChannel restChannel; + + @Mock + private RestRequest restRequest; + + private ConfigUpgradeApiAction configUpgradeApiAction; + + @Before + public void setUp() throws IOException { + setupRolesConfiguration(); + doReturn(XContentFactory.jsonBuilder()).when(restChannel).newBuilder(); + + final var actionFuture = mock(ActionFuture.class); + doReturn(mock(IndexResponse.class)).when(actionFuture).actionGet(); + doReturn(actionFuture).when(client).index(any()); + + configUpgradeApiAction = spy(new ConfigUpgradeApiAction(clusterService, threadPool, securityApiDependencies)); + + final var objectMapper = DefaultObjectMapper.objectMapper; + final var config = objectMapper.createObjectNode(); + config.set("_meta", objectMapper.createObjectNode().put("type", CType.ROLES.toLCString()).put("config_version", 2)); + config.set("kibana_read_only", objectMapper.createObjectNode().put("reserved", true)); + final var newRole = objectMapper.createObjectNode(); + newRole.put("reserved", true); + newRole.putArray("cluster_permissions").add("test-permission-1").add("test-permission-2"); + config.set("new_role", newRole); + + doReturn(config).when(configUpgradeApiAction).loadConfigFileAsJson(any()); + } + + @Test + public void testCanUpgrade_ErrorLoadingConfig() throws Exception { + // Setup + doThrow(new IOException("abc")).when(configUpgradeApiAction).loadConfigFileAsJson(any()); + + // Execute + configUpgradeApiAction.canUpgrade(restChannel, restRequest, client); + + // Assert + verify(restChannel).sendResponse(verifyResponseBody(body -> assertThat(body, containsString("see the log file to troubleshoot")))); + } + + @Test + public void testPerformUpgrade_ErrorLoadingConfig() throws Exception { + // Setup + doThrow(new IOException("abc")).when(configUpgradeApiAction).loadConfigFileAsJson(any()); + + // Execute + configUpgradeApiAction.performUpgrade(restChannel, restRequest, client); + + // Assert + verify(restChannel).sendResponse(verifyResponseBody(body -> assertThat(body, containsString("see the log file to troubleshoot")))); + } + + @Test + public void testPerformUpgrade_ErrorApplyConfig() throws Exception { + // Setup + doThrow(new RuntimeException("abc")).when(configUpgradeApiAction).patchEntities(any(), any(), any()); + + // Execute + configUpgradeApiAction.performUpgrade(restChannel, restRequest, client); + + // Assert + verify(restChannel).sendResponse(verifyResponseBody(body -> assertThat(body, containsString("see the log file to troubleshoot")))); + } + + @Test + public void testPerformUpgrade_NoDifferences() throws Exception { + // Setup + final var rolesCopy = rolesConfiguration.deepClone(); + rolesCopy.removeStatic(); // Statics are added by code, not by config files, they should be omitted + final var rolesJsonNode = Utils.convertJsonToJackson(rolesCopy, true); + doReturn(rolesJsonNode).when(configUpgradeApiAction).loadConfigFileAsJson(any()); + + // Execute + configUpgradeApiAction.performUpgrade(restChannel, restRequest, client); + + // Verify + verify(restChannel).sendResponse(verifyResponseBody(body -> assertThat(body, containsString("no differences found")))); + } + + @Test + public void testPerformUpgrade_WithDifferences() throws Exception { + // Execute + configUpgradeApiAction.performUpgrade(restChannel, restRequest, client); + + // Verify + verify(restChannel).sendResponse(argThat(response -> { + final var rawResponseBody = response.content().utf8ToString(); + final var newlineNormalizedBody = rawResponseBody.replace("\r\n", "\n"); + assertThat(newlineNormalizedBody, equalTo("{\n" + // + " \"status\" : \"OK\",\n" + // + " \"upgrades\" : {\n" + // + " \"roles\" : {\n" + // + " \"add\" : [ \"new_role\" ]\n" + // + " }\n" + // + " }\n" + // + "}")); + return true; + })); + } + + @Test + public void testConfigurationDifferences_OperationBash() throws IOException { + final var testCases = new ImmutableList.Builder(); + + testCases.add( + new OperationTestCase("Missing entry", source -> {}, updated -> updated.put("a", "1"), List.of(List.of("add", "/a", "1"))) + ); + + testCases.add( + new OperationTestCase( + "Same object", + source -> source.set("a", objectMapper.createObjectNode()), + updated -> updated.set("a", objectMapper.createObjectNode()), + List.of() + ) + ); + + testCases.add( + new OperationTestCase("Missing object", source -> source.set("a", objectMapper.createObjectNode()), updated -> {}, List.of()) + ); + + testCases.add(new OperationTestCase("Moved and identical object", source -> { + source.set("a", objectMapper.createObjectNode()); + source.set("b", objectMapper.createObjectNode()); + source.set("c", objectMapper.createObjectNode()); + }, updated -> { + updated.set("a", objectMapper.createObjectNode()); + updated.set("c", objectMapper.createObjectNode()); + updated.set("b", objectMapper.createObjectNode()); + }, List.of())); + + testCases.add(new OperationTestCase("Moved and different object", source -> { + source.set("a", objectMapper.createObjectNode()); + source.set("b", objectMapper.createObjectNode()); + source.set("c", objectMapper.createObjectNode()); + }, updated -> { + updated.set("a", objectMapper.createObjectNode()); + updated.set("c", objectMapper.createObjectNode().put("d", "1")); + updated.set("b", objectMapper.createObjectNode()); + }, List.of(List.of("add", "/c/d", "1")))); + + testCases.add(new OperationTestCase("Removed field object", source -> { + source.set("a", objectMapper.createObjectNode().put("hidden", true)); + source.set("b", objectMapper.createObjectNode()); + }, updated -> { + updated.set("a", objectMapper.createObjectNode()); + updated.set("b", objectMapper.createObjectNode()); + }, List.of(List.of("remove", "/a/hidden", "")))); + + testCases.add(new OperationTestCase("Removed field object", source -> { + final var roleA = objectMapper.createObjectNode(); + roleA.putArray("cluster_permissions").add("1").add("2").add("3"); + source.set("a", roleA); + }, updated -> { + final var roleA = objectMapper.createObjectNode(); + roleA.putArray("cluster_permissions").add("2").add("11").add("3").add("44"); + updated.set("a", roleA); + }, + List.of( + List.of("remove", "/a/cluster_permissions/0", ""), + List.of("add", "/a/cluster_permissions/1", "11"), + List.of("add", "/a/cluster_permissions/3", "44") + ) + )); + + for (final var tc : testCases.build()) { + // Setup + final var source = objectMapper.createObjectNode(); + source.set("_meta", objectMapper.createObjectNode().put("type", CType.ROLES.toLCString()).put("config_version", 2)); + tc.sourceChanges.accept(source); + final var updated = objectMapper.createObjectNode(); + tc.updates.accept(updated); + + var sourceAsConfig = SecurityDynamicConfiguration.fromJson(objectMapper.writeValueAsString(source), CType.ROLES, 2, 1, 1); + + doReturn(ValidationResult.success(sourceAsConfig)).when(configUpgradeApiAction) + .loadConfiguration(any(), anyBoolean(), anyBoolean()); + doReturn(updated).when(configUpgradeApiAction).loadConfigFileAsJson(any()); + + // Execute + var result = configUpgradeApiAction.computeDifferenceToUpdate(CType.ACTIONGROUPS); + + // Verify + result.valid(differences -> { + assertThat(differences.v1(), equalTo(CType.ACTIONGROUPS)); + assertThat(tc.name + ": Number of operations", differences.v2().size(), equalTo(tc.expectedResults.size())); + final var expectedResultsIterator = tc.expectedResults.iterator(); + differences.v2().forEach(operation -> { + final List expected = expectedResultsIterator.next(); + assertThat( + tc.name + ": Operation type" + operation.toPrettyString(), + operation.get("op").asText(), + equalTo(expected.get(0)) + ); + assertThat(tc.name + ": Path" + operation.toPrettyString(), operation.get("path").asText(), equalTo(expected.get(1))); + assertThat( + tc.name + ": Value " + operation.toPrettyString(), + operation.has("value") ? operation.get("value").asText("") : "", + equalTo(expected.get(2)) + ); + }); + }); + } + } + + static class OperationTestCase { + final String name; + final Consumer sourceChanges; + final Consumer updates; + final List> expectedResults; + + OperationTestCase( + final String name, + final Consumer sourceChanges, + final Consumer updates, + final List> expectedResults + ) { + this.name = name; + this.sourceChanges = sourceChanges; + this.updates = updates; + this.expectedResults = expectedResults; + } + + } + + private RestResponse verifyResponseBody(final Consumer test) { + return argThat(response -> { + final String content = response.content().utf8ToString(); + test.accept(content); + return true; + }); + } + +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/InternalUsersApiActionValidationTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/InternalUsersApiActionValidationTest.java index 773d356246..2af598f5d5 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/InternalUsersApiActionValidationTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/InternalUsersApiActionValidationTest.java @@ -22,6 +22,7 @@ import org.opensearch.core.rest.RestStatus; import org.opensearch.rest.RestRequest; import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.dlic.rest.validation.ValidationResult; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.securityconf.impl.v7.InternalUserV7; @@ -32,9 +33,13 @@ import org.mockito.Mock; import org.mockito.Mockito; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.when; public class InternalUsersApiActionValidationTest extends AbstractApiActionValidationTest { @@ -145,19 +150,26 @@ public void validateSecurityRolesWithMutableRolesMappingConfig() throws Exceptio // should ok to set regular role with mutable role mapping var userJson = objectMapper.createObjectNode().set("opendistro_security_roles", objectMapper.createArrayNode().add("regular_role")); var result = internalUsersApiAction.validateSecurityRoles(SecurityConfiguration.of(userJson, "some_user", configuration)); - assertTrue(result.isValid()); + assertValidationResultIsValid(result); // should be ok to set reserved role with mutable role mapping userJson = objectMapper.createObjectNode().set("opendistro_security_roles", objectMapper.createArrayNode().add("kibana_read_only")); result = internalUsersApiAction.validateSecurityRoles(SecurityConfiguration.of(userJson, "some_user", configuration)); - assertTrue(result.isValid()); + assertValidationResultIsValid(result); // should be ok to set static role with mutable role mapping userJson = objectMapper.createObjectNode().set("opendistro_security_roles", objectMapper.createArrayNode().add("all_access")); result = internalUsersApiAction.validateSecurityRoles(SecurityConfiguration.of(userJson, "some_user", configuration)); - assertTrue(result.isValid()); + assertValidationResultIsValid(result); // should not be ok to set hidden role with mutable role mapping userJson = objectMapper.createObjectNode().set("opendistro_security_roles", objectMapper.createArrayNode().add("some_hidden_role")); result = internalUsersApiAction.validateSecurityRoles(SecurityConfiguration.of(userJson, "some_user", configuration)); - assertFalse(result.isValid()); + final var errorMessage = xContentToJsonNode(result.errorMessage()).toPrettyString(); + assertThat(errorMessage, allOf(containsString("NOT_FOUND"), containsString("Resource 'some_hidden_role' is not available."))); + } + + void assertValidationResultIsValid(final ValidationResult result) { + if (!result.isValid()) { + fail("Expected valid result, error message: " + xContentToJsonNode(result.errorMessage()).toPrettyString()); + } } @Test From dd119e59dc157d19f2c5836b3af055d931f566e2 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:16:51 -0400 Subject: [PATCH 13/19] 2.13 Release notes (#4140) Signed-off-by: Stephen Crawford --- ...nsearch-security.release-notes-2.13.0.0.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 release-notes/opensearch-security.release-notes-2.13.0.0.md diff --git a/release-notes/opensearch-security.release-notes-2.13.0.0.md b/release-notes/opensearch-security.release-notes-2.13.0.0.md new file mode 100644 index 0000000000..42ccd8fff0 --- /dev/null +++ b/release-notes/opensearch-security.release-notes-2.13.0.0.md @@ -0,0 +1,30 @@ +## 2024-03-19 Version 2.13.0.0 + +Compatible with OpenSearch 2.13.0 + +### Enhancements +* Admin role for Query insights plugin ([#4022](https://github.com/opensearch-project/security/pull/4022)) +* Add query assistant role and new ml system indices ([#4143](https://github.com/opensearch-project/security/pull/4143)) +* Redact sensitive configuration values when retrieving security configuration ([#4028](https://github.com/opensearch-project/security/pull/4028)) +* v2.12 update roles.yml with new API for experimental alerting plugin feature ([#4035](https://github.com/opensearch-project/security/pull/4035)) +* Add deprecate message that TLSv1 and TLSv1.1 support will be removed in the next major version ([#4083](https://github.com/opensearch-project/security/pull/4083)) +* Log password requirement details in demo environment ([#4082](https://github.com/opensearch-project/security/pull/4082)) +* Redact sensitive URL parameters from audit logging ([#4070](https://github.com/opensearch-project/security/pull/4070)) +* Fix unconsumed parameter exception when authenticating with jwtUrlParameter ([#4065](https://github.com/opensearch-project/security/pull/4065)) +* Regenerates root-ca, kirk and esnode certificates to address already expired root ca certificate ([#4066](https://github.com/opensearch-project/security/pull/4066)) +* Add exclude_roles configuration parameter to LDAP authorization backend ([#4043](https://github.com/opensearch-project/security/pull/4043)) + +### Maintenance +* Add exlusion for logback-core to resolve CVE-2023-6378 ([#4050](https://github.com/opensearch-project/security/pull/4050)) +* Bump com.netflix.nebula.ospackage from 11.7.0 to 11.8.1 ([#4041](https://github.com/opensearch-project/security/pull/4041), [#4075](https://github.com/opensearch-project/security/pull/4075)) +* Bump Wandalen/wretry.action from 1.3.0 to 1.4.10 ([#4042](https://github.com/opensearch-project/security/pull/4042), [#4092](https://github.com/opensearch-project/security/pull/4092), [#4108](https://github.com/opensearch-project/security/pull/4108), [#4135](https://github.com/opensearch-project/security/pull/4135)) +* Bump spring_version from 5.3.31 to 5.3.33 ([#4058](https://github.com/opensearch-project/security/pull/4058), [#4131](https://github.com/opensearch-project/security/pull/4131)) +* Bump org.scala-lang:scala-library from 2.13.12 to 2.13.13 ([#4076](https://github.com/opensearch-project/security/pull/4076)) +* Bump com.google.googlejavaformat:google-java-format from 1.19.1 to 1.21.0 ([#4078](https://github.com/opensearch-project/security/pull/4078), [#4110](https://github.com/opensearch-project/security/pull/4110)) +* Bump ch.qos.logback:logback-classic from 1.2.13 to 1.5.3 ([#4091](https://github.com/opensearch-project/security/pull/4091), [#4111](https://github.com/opensearch-project/security/pull/4111)) +* Bump com.fasterxml.woodstox:woodstox-core from 6.6.0 to 6.6.1 ([#4093](https://github.com/opensearch-project/security/pull/4093)) +* Bump kafka_version from 3.5.1 to 3.7.0 ([#4095](https://github.com/opensearch-project/security/pull/4095)) +* Bump jakarta.xml.bind:jakarta.xml.bind-api from 4.0.1 to 4.0.2 ([#4109](https://github.com/opensearch-project/security/pull/4109)) +* Bump org.apache.zookeeper:zookeeper from 3.9.1. to 3.9.2 ([#4130](https://github.com/opensearch-project/security/pull/4130)) +* Bump org.awaitility:awaitility from 4.2.0 to 4.2.1 ([#4133](https://github.com/opensearch-project/security/pull/4133)) +* Bump com.google.errorprone:error_prone_annotations from 2.25.0 to 2.26.1 ([#4132](https://github.com/opensearch-project/security/pull/4132)) From 6f79d094b80983f36b1b52d4703093f2583ec61e Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 22 Mar 2024 09:33:57 -0400 Subject: [PATCH 14/19] Update Log4JSink Default from sgaudit to audit and add test for default values (#4146) Signed-off-by: Craig Perkins --- .../org/opensearch/security/auditlog/sink/Log4JSink.java | 2 +- .../opensearch/security/auditlog/sink/SinkProviderTest.java | 6 ++++++ .../auditlog/endpoints/sink/configuration_all_variants.yml | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java index f01043fa21..cf535e48b1 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java @@ -27,7 +27,7 @@ public final class Log4JSink extends AuditLogSink { public Log4JSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) { super(name, settings, settingsPrefix, fallbackSink); - loggerName = settings.get(settingsPrefix + ".log4j.logger_name", "sgaudit"); + loggerName = settings.get(settingsPrefix + ".log4j.logger_name", "audit"); auditLogger = LogManager.getLogger(loggerName); logLevel = Level.toLevel(settings.get(settingsPrefix + ".log4j.level", "INFO").toUpperCase()); enabled = auditLogger.isEnabled(logLevel); diff --git a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTest.java b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTest.java index 5e3203261f..af8204a5c7 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTest.java @@ -88,6 +88,12 @@ public void testConfiguration() throws Exception { Assert.assertEquals("loggername", lsink.loggerName); Assert.assertEquals(Level.DEBUG, lsink.logLevel); + sink = provider.getSink("endpoint13"); + Assert.assertEquals(Log4JSink.class, sink.getClass()); + lsink = (Log4JSink) sink; + Assert.assertEquals("audit", lsink.loggerName); + Assert.assertEquals(Level.INFO, lsink.logLevel); + } @Test diff --git a/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml b/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml index f1c8620e88..82565ee3ec 100644 --- a/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml +++ b/src/test/resources/auditlog/endpoints/sink/configuration_all_variants.yml @@ -45,3 +45,5 @@ plugins.security: config: log4j.logger_name: loggername log4j.level: invalid + endpoint13: + type: log4j From 03db12b9dc1b9c81272e34b1e78ecd0d0ad925d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:15:34 +0000 Subject: [PATCH 15/19] Bump net.shibboleth.utilities:java-support from 8.4.0 to 8.4.1 (#4163) Bumps net.shibboleth.utilities:java-support from 8.4.0 to 8.4.1. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=net.shibboleth.utilities:java-support&package-manager=gradle&previous-version=8.4.0&new-version=8.4.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 7fcd272c32..f74d51d371 100644 --- a/build.gradle +++ b/build.gradle @@ -617,7 +617,7 @@ dependencies { testImplementation 'org.apache.camel:camel-xmlsecurity:3.22.1' //OpenSAML - implementation 'net.shibboleth.utilities:java-support:8.4.0' + implementation 'net.shibboleth.utilities:java-support:8.4.1' implementation "com.onelogin:java-saml:${one_login_java_saml}" implementation "com.onelogin:java-saml-core:${one_login_java_saml}" implementation "org.opensaml:opensaml-core:${open_saml_version}" From f2e9b3ef357cdd4b1572c7e7f23a731ba476bff7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:16:07 +0000 Subject: [PATCH 16/19] Bump open_saml_version from 4.3.0 to 4.3.1 (#4161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps `open_saml_version` from 4.3.0 to 4.3.1. Updates `org.opensaml:opensaml-core` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-security-impl` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-security-api` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-xmlsec-api` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-xmlsec-impl` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-saml-api` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-saml-impl` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-messaging-api` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-profile-api` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-soap-api` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-soap-impl` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-storage-api` from 4.3.0 to 4.3.1 Updates `org.opensaml:opensaml-messaging-impl` from 4.3.0 to 4.3.1 Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 f74d51d371..97b4409c13 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ buildscript { common_utils_version = System.getProperty("common_utils.version", '3.0.0.0-SNAPSHOT') kafka_version = '3.7.0' apache_cxf_version = '4.0.4' - open_saml_version = '4.3.0' + open_saml_version = '4.3.1' one_login_java_saml = '2.9.0' jjwt_version = '0.12.5' guava_version = '32.1.3-jre' From bb1b90044955547c1addeec41602ee39c247b55d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:16:42 +0000 Subject: [PATCH 17/19] Bump net.minidev:accessors-smart from 2.5.0 to 2.5.1 (#4162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [net.minidev:accessors-smart](https://github.com/netplex/json-smart-v2) from 2.5.0 to 2.5.1.
Release notes

Sourced from net.minidev:accessors-smart's releases.

V 2.5.1

What's Changed

New Contributors

Full Changelog: https://github.com/netplex/json-smart-v2/compare/2.5.0...2.5.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=net.minidev:accessors-smart&package-manager=gradle&previous-version=2.5.0&new-version=2.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 97b4409c13..187f0483bf 100644 --- a/build.gradle +++ b/build.gradle @@ -599,7 +599,7 @@ dependencies { implementation "org.apache.kafka:kafka-clients:${kafka_version}" - runtimeOnly 'net.minidev:accessors-smart:2.5.0' + runtimeOnly 'net.minidev:accessors-smart:2.5.1' runtimeOnly "org.apache.cxf:cxf-core:${apache_cxf_version}" implementation "org.apache.cxf:cxf-rt-rs-json-basic:${apache_cxf_version}" From c64d0aa66654e43886377c648811d07d0840af9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:19:19 +0000 Subject: [PATCH 18/19] Bump org.ow2.asm:asm from 9.6 to 9.7 (#4164) Bumps org.ow2.asm:asm from 9.6 to 9.7. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.ow2.asm:asm&package-manager=gradle&previous-version=9.6&new-version=9.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 187f0483bf..ea392a8eac 100644 --- a/build.gradle +++ b/build.gradle @@ -612,7 +612,7 @@ dependencies { compileOnly 'com.google.errorprone:error_prone_annotations:2.26.1' runtimeOnly 'com.sun.istack:istack-commons-runtime:4.2.0' runtimeOnly 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.2' - runtimeOnly 'org.ow2.asm:asm:9.6' + runtimeOnly 'org.ow2.asm:asm:9.7' testImplementation 'org.apache.camel:camel-xmlsecurity:3.22.1' From a731e62f8a52420645473c8279081ae46bfb86b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 18:06:33 +0100 Subject: [PATCH 19/19] Bump Wandalen/wretry.action from 1.4.10 to 2.1.0 (#4165) Bumps [Wandalen/wretry.action](https://github.com/wandalen/wretry.action) from 1.4.10 to 2.1.0.
Commits
  • f062299 version 2.1.0
  • 1900814 Merge pull request #144 from dmvict/master
  • 1903bde Extend action, add option github_token to work with private actions
  • 4ca71ac version 2.0.0
  • aa38070 Merge pull request #142 from dmvict/master
  • 86e0a3a Improve Readme.md, fix some mistakes, add comments
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Wandalen/wretry.action&package-manager=github_actions&previous-version=1.4.10&new-version=2.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 0595106ce7..68e966f56e 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@v1.4.10 + uses: Wandalen/wretry.action@v2.1.0 with: attempt_limit: 5 attempt_delay: 2000