diff --git a/.github/actions/run-cypress-tests/action.yml b/.github/actions/run-cypress-tests/action.yml index d77adf5a2..6a85d3ff4 100644 --- a/.github/actions/run-cypress-tests/action.yml +++ b/.github/actions/run-cypress-tests/action.yml @@ -51,7 +51,7 @@ runs: # OSD bootstrap - name: Run Dashboard with Security Dashboards Plugin - uses: derek-ho/setup-opensearch-dashboards@v1 + uses: ./.github/actions/setup-opensearch-dashboards with: plugin_name: security-dashboards-plugin opensearch_dashboards_yml: ${{ inputs.dashboards_config_file }} diff --git a/.github/actions/setup-opensearch-dashboards-for-integ-tests/action.yml b/.github/actions/setup-opensearch-dashboards-for-integ-tests/action.yml new file mode 100644 index 000000000..ac06ef036 --- /dev/null +++ b/.github/actions/setup-opensearch-dashboards-for-integ-tests/action.yml @@ -0,0 +1,170 @@ +name: 'Starts Dashboards with Plugin' +description: 'Installs OpenSearch Dashboard with a plugin from github, then checks out the correct dashboards version for the plugin, configures npm/yarn, bootstraps Dashboards, and starts the instance' + +inputs: + plugin_name: + description: 'The name of the plugin to use, such as security-dashboards-plugin' + required: true + + built_plugin_name: + description: 'The name of the plugin after building, if doing zip install' + required: false + + built_plugin_suffix: + description: 'The suffix of the plugin after building, if doing zip instll. Leave empty to indicate default OSD build behavior' + required: false + + install_zip: + description: 'Whether the setup should be done using the install plugin flow. Leave empty to indicate dev setup. Only applicable for linux for now' + required: false + + plugin_repo: + description: 'The repo of the plugin to use. Leave empty to indicate the repo which is running this action' + required: false + + opensearch_dashboards_yml: + description: 'The file to replace opensearch_dashboards.yml. Leave empty to use the default' + required: false + + plugin_branch: + description: 'The branch of the repo of the plugin to use. Leave empty to indicate the branch which is running this action' + required: false + +outputs: + dashboards-directory: + description: "The directory where the dashboards has been configured" + value: ${{ steps.determine-dashboards-directory.outputs.dashboards-directory }} + + dashboards-binary-directory: + description: "The directory of the dashboards binary that was configured" + value: ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }} + + plugin-directory: + description: "The directory where the plugin has been configured" + value: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + + +runs: + using: "composite" + steps: + - id: determine-dashboards-directory + if: ${{ inputs.install_zip != 'true' }} + run: echo "dashboards-directory=OpenSearch-Dashboards" >> $GITHUB_OUTPUT + shell: bash + + - id: determine-plugin-directory + run: echo "::set-output name=plugin-directory::./OpenSearch-Dashboards/plugins/${{ inputs.plugin_name }}" + shell: bash + + - uses: actions/checkout@v2 + with: + path: OpenSearch-Dashboards + repository: cwperks/OpenSearch-Dashboards + ref: 'support-repeated-plugin' + fetch-depth: 0 + filter: | + cypress + test + + - uses: actions/checkout@v2 + with: + path: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + + - id: osd-version + run: | + echo "::set-output name=osd-version::$(jq -r '.opensearchDashboards.version | split(".") | .[:2] | join(".")' package.json)" + echo "::set-output name=osd-x-version::$(jq -r '.opensearchDashboards.version | split(".") | .[0]' package.json).x" + echo "::set-output name=plugin-version::$(jq -r '.opensearchDashboardsVersion' opensearch_dashboards.json)" + working-directory: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + shell: bash + + - id: branch-switch-if-possible + continue-on-error: true # Defaults onto main if the branch switch doesn't work + if: ${{ steps.osd-version.outputs.osd-version }} + run: git checkout ${{ steps.osd-version.outputs.osd-version }} || git checkout ${{ steps.osd-version.outputs.osd-x-version }} + working-directory: ./OpenSearch-Dashboards + shell: bash + + - id: tool-versions + run: | + echo "node_version=$(cat .node-version)" >> $GITHUB_OUTPUT + echo "yarn_version=$(jq -r '.engines.yarn' package.json)" >> $GITHUB_OUTPUT + working-directory: OpenSearch-Dashboards + shell: bash + + - uses: actions/setup-node@v1 + with: + node-version: ${{ steps.tool-versions.outputs.node_version }} + registry-url: 'https://registry.npmjs.org' + + - name: Setup Opensearch Dashboards + run: | + npm uninstall -g yarn + echo "Installing yarn ${{ steps.tool-versions.outputs.yarn_version }}" + npm i -g yarn@${{ steps.tool-versions.outputs.yarn_version }} + yarn cache clean + yarn add sha.js + working-directory: OpenSearch-Dashboards + shell: bash + + - name: Bootstrap the OpenSearch Dashboard + uses: nick-fields/retry@v2 + with: + timeout_minutes: 20 + max_attempts: 2 + command: yarn --cwd OpenSearch-Dashboards osd bootstrap --oss --single-version=loose # loose is passed in to ignore version conflicts on cypress version used in OSD and this repo + + - name: Download OpenSearch for Linux + uses: peternied/download-file@v2 + if: ${{ inputs.install_zip == 'true' && runner.os == 'Linux' }} + with: + url: https://ci.opensearch.org/ci/dbc/distribution-build-opensearch-dashboards/${{ steps.osd-version.outputs.plugin-version }}/latest/linux/x64/tar/builds/opensearch-dashboards/dist/opensearch-dashboards-min-${{ steps.osd-version.outputs.plugin-version }}-linux-x64.tar.gz + + # Extract downloaded zip + - name: Extract downloaded tar + if: ${{ inputs.install_zip == 'true' && runner.os == 'Linux' }} + run: | + tar -xzf opensearch-*.tar.gz + rm -f opensearch-*.tar.gz + shell: bash + + - id: determine-dashboards-directory-zip-install + if: ${{ inputs.install_zip == 'true' }} + run: echo "dashboards-directory=opensearch-dashboards-${{ steps.osd-version.outputs.plugin-version }}-linux-x64" >> $GITHUB_OUTPUT + shell: bash + + - name: Build the plugin + if: ${{ inputs.install_zip == 'true' }} + run: | + yarn build + working-directory: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + shell: bash + + - name: Install the plugin zip + if: ${{ inputs.install_zip == 'true' && inputs.built_plugin_suffix == '' }} + run: | + ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }}/bin/opensearch-dashboards-plugin install file:$(pwd)/OpenSearch-Dashboards/plugins/${{ inputs.plugin_name }}/build/${{ inputs.built_plugin_name }}-${{env.PLUGIN_VERSION}}.zip + shell: bash + + - name: Install the plugin zip + if: ${{ inputs.install_zip == 'true' && inputs.built_plugin_suffix != '' }} + run: | + ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }}/bin/opensearch-dashboards-plugin install file:$(pwd)/OpenSearch-Dashboards/plugins/${{ inputs.plugin_name }}/build/${{ inputs.built_plugin_name }}-${{inputs.built_plugin_suffix}}.zip + shell: bash + + - name: Replace opensearch_dashboards.yml file if applicable binary + if: ${{ inputs.opensearch_dashboards_yml != '' && inputs.install_zip == 'true' }} + run: | + mv ${{ inputs.opensearch_dashboards_yml }} ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }}/config/opensearch_dashboards.yml + shell: bash + + - name: Replace opensearch_dashboards.yml file if applicable dev + if: ${{ inputs.opensearch_dashboards_yml != '' && inputs.install_zip != 'true' }} + run: | + mv ${{ inputs.opensearch_dashboards_yml }} ./OpenSearch-Dashboards/config/opensearch_dashboards.yml + shell: bash + + - name: Print contents + run: | + cat ./OpenSearch-Dashboards/config/opensearch_dashboards.yml + shell: bash \ No newline at end of file diff --git a/.github/actions/setup-opensearch-dashboards/action.yml b/.github/actions/setup-opensearch-dashboards/action.yml new file mode 100644 index 000000000..fa691a079 --- /dev/null +++ b/.github/actions/setup-opensearch-dashboards/action.yml @@ -0,0 +1,269 @@ +name: 'Starts Dashboards with Plugin' +description: 'Installs OpenSearch Dashboard with a plugin from github, then checks out the correct dashboards version for the plugin, configures npm/yarn, bootstraps Dashboards, and starts the instance' + +inputs: + plugin_name: + description: 'The name of the plugin to use, such as security-dashboards-plugin' + required: true + + built_plugin_name: + description: 'The name of the plugin after building, if doing zip install' + required: false + + built_plugin_suffix: + description: 'The suffix of the plugin after building, if doing zip instll. Leave empty to indicate default OSD build behavior' + required: false + + install_zip: + description: 'Whether the setup should be done using the install plugin flow. Leave empty to indicate dev setup. Only applicable for linux for now' + required: false + + plugin_repo: + description: 'The repo of the plugin to use. Leave empty to indicate the repo which is running this action' + required: false + + opensearch_dashboards_yml: + description: 'The file to replace opensearch_dashboards.yml. Leave empty to use the default' + required: false + + plugin_branch: + description: 'The branch of the repo of the plugin to use. Leave empty to indicate the branch which is running this action' + required: false + +outputs: + dashboards-directory: + description: "The directory where the dashboards has been configured" + value: ${{ steps.determine-dashboards-directory.outputs.dashboards-directory }} + + dashboards-binary-directory: + description: "The directory of the dashboards binary that was configured" + value: ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }} + + plugin-directory: + description: "The directory where the plugin has been configured" + value: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + + +runs: + using: "composite" + steps: + - id: determine-dashboards-directory + if: ${{ inputs.install_zip != 'true' }} + run: echo "dashboards-directory=OpenSearch-Dashboards" >> $GITHUB_OUTPUT + shell: bash + + - id: determine-plugin-directory + run: echo "::set-output name=plugin-directory::./OpenSearch-Dashboards/plugins/${{ inputs.plugin_name }}" + shell: bash + + - uses: actions/checkout@v2 + with: + path: OpenSearch-Dashboards + repository: cwperks/OpenSearch-Dashboards + ref: 'support-repeated-plugin' + fetch-depth: 0 + filter: | + cypress + test + + - uses: actions/checkout@v2 + with: + path: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + + - uses: actions/checkout@v2 + with: + path: ./OpenSearch-Dashboards/plugins/security-admin-dashboards-plugin + + - id: osd-version + run: | + echo "::set-output name=osd-version::$(jq -r '.opensearchDashboards.version | split(".") | .[:2] | join(".")' package.json)" + echo "::set-output name=osd-x-version::$(jq -r '.opensearchDashboards.version | split(".") | .[0]' package.json).x" + echo "::set-output name=plugin-version::$(jq -r '.opensearchDashboardsVersion' opensearch_dashboards.json)" + working-directory: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + shell: bash + + # - id: branch-switch-if-possible + # continue-on-error: true # Defaults onto main if the branch switch doesn't work + # if: ${{ steps.osd-version.outputs.osd-version }} + # run: git checkout ${{ steps.osd-version.outputs.osd-version }} || git checkout ${{ steps.osd-version.outputs.osd-x-version }} + # working-directory: ./OpenSearch-Dashboards + # shell: bash + + - id: tool-versions + run: | + echo "node_version=$(cat .node-version)" >> $GITHUB_OUTPUT + echo "yarn_version=$(jq -r '.engines.yarn' package.json)" >> $GITHUB_OUTPUT + working-directory: OpenSearch-Dashboards + shell: bash + + - uses: actions/setup-node@v1 + with: + node-version: ${{ steps.tool-versions.outputs.node_version }} + registry-url: 'https://registry.npmjs.org' + + - name: Setup Opensearch Dashboards + run: | + npm uninstall -g yarn + echo "Installing yarn ${{ steps.tool-versions.outputs.yarn_version }}" + npm i -g yarn@${{ steps.tool-versions.outputs.yarn_version }} + yarn cache clean + yarn add sha.js + working-directory: OpenSearch-Dashboards + shell: bash + + - name: Create opensearch_dashboards.json for security admin dashboards plugin + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + rm ./OpenSearch-Dashboards/plugins/security-admin-dashboards-plugin/opensearch_dashboards.json + cat << 'EOT' > ./OpenSearch-Dashboards/plugins/security-admin-dashboards-plugin/opensearch_dashboards.json + { + "id": "securityAdminDashboards", + "version": "3.0.0.0", + "opensearchDashboardsVersion": "3.0.0", + "configPath": [ + "opensearch_security_admin" + ], + "requiredPlugins": [ + "navigation", + "savedObjectsManagement" + ], + "optionalPlugins": [ + "managementOverview", + "dataSource", + "dataSourceManagement" + ], + "server": true, + "ui": true + } + EOT + + - name: Create package.json for security admin dashboards plugin + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + rm ./OpenSearch-Dashboards/plugins/security-admin-dashboards-plugin/package.json + cat << 'EOT' > ./OpenSearch-Dashboards/plugins/security-admin-dashboards-plugin/package.json + { + "name": "opensearch-security-admin-dashboards", + "version": "3.0.0.0", + "main": "target/plugins/opensearch_security_dashboards", + "opensearchDashboards": { + "version": "3.0.0", + "templateVersion": "3.0.0" + }, + "license": "Apache-2.0", + "homepage": "https://github.com/opensearch-project/security-dashboards-plugin", + "scripts": { + "cypress:open": "cypress open", + "cypress:run": "cypress run", + "plugin-helpers": "node ../../scripts/plugin_helpers", + "osd": "node ../../scripts/osd", + "opensearch": "node ../../scripts/opensearch", + "build": "yarn plugin-helpers build && node build_tools/rename_zip.js", + "start": "node ../../scripts/opensearch-dashboards --dev", + "lint:es": "node ../../scripts/eslint", + "lint:style": "node ../../scripts/stylelint", + "lint": "yarn run lint:es && yarn run lint:style", + "runIdp": "node ./test/jest_integration/runIdpServer.js", + "test:jest_server": "ADMIN_PASSWORD=$ADMIN_PASSWORD node ./test/run_jest_tests.js --config ./test/jest.config.server.js", + "test:jest_ui": "node ./test/run_jest_tests.js --config ./test/jest.config.ui.js", + "prepare": "husky install" + }, + "devDependencies": { + "@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards", + "@testing-library/react-hooks": "^7.0.2", + "@types/hapi__wreck": "^15.0.1", + "cypress": "^13.6.0", + "cypress-mochawesome-reporter": "^3.3.0", + "eslint-plugin-cypress": "^2.8.1", + "eslint-plugin-unused-imports": "3.1.0", + "gulp-rename": "2.0.0", + "husky": "^8.0.0", + "jose": "^5.2.4", + "minimist": "^1.2.8", + "saml-idp": "^1.2.1", + "selfsigned": "^2.0.1", + "typescript": "4.0.2" + }, + "dependencies": { + "@hapi/cryptiles": "5.0.0", + "@hapi/wreck": "^17.1.0", + "html-entities": "1.3.1", + "zxcvbn": "^4.4.2" + }, + "resolutions": { + "selenium-webdriver": "4.10.0", + "glob-parent": "^5.1.2", + "debug": "^4.3.4" + } + } + EOT + + - name: Bootstrap the OpenSearch Dashboard + uses: nick-fields/retry@v2 + with: + timeout_minutes: 20 + max_attempts: 2 + command: yarn --cwd OpenSearch-Dashboards osd bootstrap --oss --single-version=loose # loose is passed in to ignore version conflicts on cypress version used in OSD and this repo + + - name: Download OpenSearch for Linux + uses: peternied/download-file@v2 + if: ${{ inputs.install_zip == 'true' && runner.os == 'Linux' }} + with: + url: https://ci.opensearch.org/ci/dbc/distribution-build-opensearch-dashboards/${{ steps.osd-version.outputs.plugin-version }}/latest/linux/x64/tar/builds/opensearch-dashboards/dist/opensearch-dashboards-min-${{ steps.osd-version.outputs.plugin-version }}-linux-x64.tar.gz + + # Extract downloaded zip + - name: Extract downloaded tar + if: ${{ inputs.install_zip == 'true' && runner.os == 'Linux' }} + run: | + tar -xzf opensearch-*.tar.gz + rm -f opensearch-*.tar.gz + shell: bash + + - id: determine-dashboards-directory-zip-install + if: ${{ inputs.install_zip == 'true' }} + run: echo "dashboards-directory=opensearch-dashboards-${{ steps.osd-version.outputs.plugin-version }}-linux-x64" >> $GITHUB_OUTPUT + shell: bash + + - name: Build the plugin + if: ${{ inputs.install_zip == 'true' }} + run: | + yarn build + working-directory: ${{ steps.determine-plugin-directory.outputs.plugin-directory }} + shell: bash + + - name: Install the plugin zip + if: ${{ inputs.install_zip == 'true' && inputs.built_plugin_suffix == '' }} + run: | + ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }}/bin/opensearch-dashboards-plugin install file:$(pwd)/OpenSearch-Dashboards/plugins/${{ inputs.plugin_name }}/build/${{ inputs.built_plugin_name }}-${{env.PLUGIN_VERSION}}.zip + shell: bash + + - name: Install the plugin zip + if: ${{ inputs.install_zip == 'true' && inputs.built_plugin_suffix != '' }} + run: | + ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }}/bin/opensearch-dashboards-plugin install file:$(pwd)/OpenSearch-Dashboards/plugins/${{ inputs.plugin_name }}/build/${{ inputs.built_plugin_name }}-${{inputs.built_plugin_suffix}}.zip + shell: bash + + - name: Replace opensearch_dashboards.yml file if applicable binary + if: ${{ inputs.opensearch_dashboards_yml != '' && inputs.install_zip == 'true' }} + run: | + mv ${{ inputs.opensearch_dashboards_yml }} ${{ steps.determine-dashboards-directory-zip-install.outputs.dashboards-directory }}/config/opensearch_dashboards.yml + shell: bash + + - name: Replace opensearch_dashboards.yml file if applicable dev + if: ${{ inputs.opensearch_dashboards_yml != '' && inputs.install_zip != 'true' }} + run: | + mv ${{ inputs.opensearch_dashboards_yml }} ./OpenSearch-Dashboards/config/opensearch_dashboards.yml + shell: bash + + - name: Add to opensearch_dashboards.yml config file + run: | + echo 'opensearch_security.configuration.admin_pages_enabled: false' >> ./OpenSearch-Dashboards/config/opensearch_dashboards.yml + echo 'opensearch_security_admin.configuration.session_management_enabled: false' >> ./OpenSearch-Dashboards/config/opensearch_dashboards.yml + shell: bash + + - name: Print contents + run: | + cat ./OpenSearch-Dashboards/config/opensearch_dashboards.yml + shell: bash \ No newline at end of file diff --git a/.github/workflows/cypress-test-multiauth-e2e.yml b/.github/workflows/cypress-test-multiauth-e2e.yml index bb7985f9b..9f06f5e88 100644 --- a/.github/workflows/cypress-test-multiauth-e2e.yml +++ b/.github/workflows/cypress-test-multiauth-e2e.yml @@ -1,6 +1,6 @@ name: Snapshot based E2E SAML multi-auth tests workflow -on: [ push, pull_request ] +on: [ pull_request ] env: OPENSEARCH_VERSION: '3.0.0' diff --git a/.github/workflows/cypress-test-multidatasources-disabled-e2e.yml b/.github/workflows/cypress-test-multidatasources-disabled-e2e.yml index e41de5fab..4e0fd5af3 100644 --- a/.github/workflows/cypress-test-multidatasources-disabled-e2e.yml +++ b/.github/workflows/cypress-test-multidatasources-disabled-e2e.yml @@ -1,6 +1,6 @@ name: E2E multi datasources disabled workflow -on: [ push, pull_request ] +on: [ pull_request ] env: OPENSEARCH_VERSION: '3.0.0' diff --git a/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml b/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml index 162941cf1..2ebb27bc5 100644 --- a/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml +++ b/.github/workflows/cypress-test-multidatasources-enabled-e2e.yml @@ -1,6 +1,6 @@ name: E2E multi datasources enabled workflow -on: [ push, pull_request ] +on: [ pull_request ] env: OPENSEARCH_VERSION: '3.0.0' @@ -94,6 +94,7 @@ jobs: opensearch.password: "kibanaserver" opensearch.requestHeadersWhitelist: [ authorization,securitytenant ] opensearch_security.multitenancy.enabled: true + opensearch_security_admin.multitenancy.enabled: true opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"] opensearch_security.readonly_mode.roles: ["kibana_read_only"] opensearch_security.cookie.secure: false diff --git a/.github/workflows/cypress-test-oidc-e2e.yml b/.github/workflows/cypress-test-oidc-e2e.yml index 11e51d390..b99725d2d 100644 --- a/.github/workflows/cypress-test-oidc-e2e.yml +++ b/.github/workflows/cypress-test-oidc-e2e.yml @@ -1,6 +1,6 @@ name: Snapshot based E2E OIDC tests workflow -on: [ push, pull_request ] +on: [ pull_request ] env: OPENSEARCH_VERSION: '3.0.0' diff --git a/.github/workflows/cypress-test-saml-e2e.yml b/.github/workflows/cypress-test-saml-e2e.yml index 3025c2cde..c3a93f39a 100644 --- a/.github/workflows/cypress-test-saml-e2e.yml +++ b/.github/workflows/cypress-test-saml-e2e.yml @@ -1,6 +1,6 @@ name: Snapshot based E2E SAML tests workflow -on: [ push, pull_request ] +on: [ pull_request ] env: OPENSEARCH_VERSION: '3.0.0' diff --git a/.github/workflows/cypress-test-tenancy-disabled.yml b/.github/workflows/cypress-test-tenancy-disabled.yml index 024dd9cbc..ec2f72a68 100644 --- a/.github/workflows/cypress-test-tenancy-disabled.yml +++ b/.github/workflows/cypress-test-tenancy-disabled.yml @@ -1,6 +1,6 @@ name: Cypress Tests Multitenancy Disabled -on: [ push, pull_request ] +on: [ pull_request ] env: TEST_BROWSER_HEADLESS: 1 @@ -70,7 +70,7 @@ jobs: EOT - name: Run Dashboard with Security Dashboards Plugin - uses: derek-ho/setup-opensearch-dashboards@v1 + uses: ./.github/actions/setup-opensearch-dashboards with: plugin_name: security-dashboards-plugin opensearch_dashboards_yml: tenancy-disabled-opensearch-dashboards-config.yml @@ -80,7 +80,7 @@ jobs: cd ./OpenSearch-Dashboards nohup yarn start --no-base-path --no-watch & sleep 500 - git clone https://github.com/opensearch-project/opensearch-dashboards-functional-test.git + git clone -b security-admin-dashboards-plugin https://github.com/cwperks/opensearch-dashboards-functional-test.git cd opensearch-dashboards-functional-test npm install cypress --save-dev - yarn cypress:run-with-security --browser chrome --spec "cypress/integration/plugins/security-dashboards-plugin/inaccessible_tenancy_features.js" + yarn cypress:run-with-security --browser chrome --spec "cypress/integration/plugins/security/inaccessible_tenancy_features.js" diff --git a/.github/workflows/cypress-test.yml b/.github/workflows/cypress-test.yml index c9a85ac6f..63610e9da 100644 --- a/.github/workflows/cypress-test.yml +++ b/.github/workflows/cypress-test.yml @@ -1,6 +1,6 @@ name: Cypress Tests -on: [ push, pull_request ] +on: [ pull_request ] env: TEST_BROWSER_HEADLESS: 1 @@ -68,11 +68,11 @@ jobs: opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"] opensearch_security.readonly_mode.roles: ["kibana_read_only"] opensearch_security.cookie.secure: false - opensearch_security.multitenancy.enable_aggregation_view: true + opensearch_security_admin.multitenancy.enabled: true EOT - name: Run Dashboard with Security Dashboards Plugin - uses: derek-ho/setup-opensearch-dashboards@v1 + uses: ./.github/actions/setup-opensearch-dashboards with: plugin_name: security-dashboards-plugin opensearch_dashboards_yml: cypress-opensearch-dashboards-config.yml @@ -82,9 +82,8 @@ jobs: cd ./OpenSearch-Dashboards nohup yarn start --no-base-path --no-watch & sleep 500 - git clone https://github.com/opensearch-project/opensearch-dashboards-functional-test.git + git clone -b security-admin-dashboards-plugin https://github.com/cwperks/opensearch-dashboards-functional-test.git cd opensearch-dashboards-functional-test npm install cypress --save-dev - yarn cypress:run-with-security-and-aggregation-view --browser chrome --spec "cypress/integration/plugins/security-dashboards-plugin/aggregation_view.js" yarn cypress:run-with-security --browser chrome --spec "cypress/integration/plugins/security-dashboards-plugin/multi_tenancy.js" yarn cypress:run-with-security --browser chrome --spec "cypress/integration/plugins/security-dashboards-plugin/default_tenant.js" diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 2126ff1d0..fced99d34 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -1,6 +1,6 @@ name: Integration Tests -on: [push, pull_request] +on: [pull_request] env: TEST_BROWSER_HEADLESS: 1 @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest , windows-latest ] + os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: @@ -81,7 +81,7 @@ jobs: sudo rm -rf "$AGENT_TOOLSDIRECTORY" - id: install-dashboards - uses: derek-ho/setup-opensearch-dashboards@v1 + uses: ./.github/actions/setup-opensearch-dashboards-for-integ-tests with: plugin_name: security-dashboards-plugin diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 7fd07c6cb..0bd72a33c 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -1,6 +1,6 @@ name: Unit Tests -on: [ push, pull_request ] +on: [ pull_request ] jobs: unit-tests: @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest , windows-latest, macos-latest ] + os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v2 - id: install-dashboards - uses: derek-ho/setup-opensearch-dashboards@v1 + uses: ./.github/actions/setup-opensearch-dashboards with: plugin_name: security-dashboards-plugin diff --git a/.github/workflows/verify-binary-installation.yml b/.github/workflows/verify-binary-installation.yml index c19bc3c81..721732ff4 100644 --- a/.github/workflows/verify-binary-installation.yml +++ b/.github/workflows/verify-binary-installation.yml @@ -1,6 +1,6 @@ name: 'Install Dashboards with Plugin via Binary' -on: [push, pull_request] +on: [pull_request] env: OPENSEARCH_VERSION: '3.0.0' CI: 1 @@ -67,7 +67,7 @@ jobs: - name: Run Dashboard with Security Dashboards Plugin id: setup-dashboards - uses: derek-ho/setup-opensearch-dashboards@v1 + uses: ./.github/actions/setup-opensearch-dashboards with: plugin_name: security-dashboards-plugin built_plugin_name: security-dashboards diff --git a/common/index.ts b/common/index.ts index 84445486d..6336b645d 100644 --- a/common/index.ts +++ b/common/index.ts @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -export const PLUGIN_ID = 'opensearchDashboardsSecurity'; -export const PLUGIN_NAME = 'security-dashboards-plugin'; +export const PLUGIN_ID = 'opensearchDashboardsSecurityAdmin'; +export const PLUGIN_NAME = 'security-admin-dashboards-plugin'; export const APP_ID_LOGIN = 'login'; export const APP_ID_CUSTOMERROR = 'customerror'; diff --git a/public/plugin.ts b/public/plugin.ts index ca9798a94..110cf7523 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -63,6 +63,8 @@ async function hasApiPermission(core: CoreSetup): Promise { } const DEFAULT_READONLY_ROLES = ['kibana_read_only']; +const DEFAULT_ADMIN_PAGES_ENABLED = true; +const DEFAULT_MANAGE_SESSION_ENABLED = true; const APP_ID_HOME = 'home'; const APP_ID_DASHBOARDS = 'dashboards'; // OpenSearchDashboards app is for legacy url migration @@ -86,105 +88,123 @@ export class SecurityPlugin core: CoreSetup, deps: SecurityPluginSetupDependencies ): Promise { - const apiPermission = await hasApiPermission(core); - const config = this.initializerContext.config.get(); - const accountInfo = (await fetchAccountInfoSafe(core.http))?.data; - const multitenancyEnabled = (await getDashboardsInfoSafe(core.http))?.multitenancy_enabled; - const isReadonly = accountInfo?.roles.some((role) => - (config.readonly_mode?.roles || DEFAULT_READONLY_ROLES).includes(role) - ); + const manageSessionEnabled = + config.configuration?.session_management_enabled !== undefined + ? config.configuration?.session_management_enabled + : DEFAULT_MANAGE_SESSION_ENABLED; + + const adminPagesEnabled = + config.configuration?.admin_pages_enabled !== undefined + ? config.configuration?.admin_pages_enabled + : DEFAULT_ADMIN_PAGES_ENABLED; + + let apiPermission: boolean | undefined; + if (adminPagesEnabled) { + apiPermission = await hasApiPermission(core); + if (apiPermission) { + core.application.register({ + id: PLUGIN_NAME, + title: 'Security', + order: 9050, + mount: async (params: AppMountParameters) => { + const { renderApp } = await import('./apps/configuration/configuration-app'); + const [coreStart, depsStart] = await core.getStartServices(); + + // merge OpenSearchDashboards yml configuration + includeClusterPermissions(config.clusterPermissions.include); + includeIndexPermissions(config.indexPermissions.include); + + excludeFromDisabledTransportCategories(config.disabledTransportCategories.exclude); + excludeFromDisabledRestCategories(config.disabledRestCategories.exclude); + + return renderApp( + coreStart, + depsStart as SecurityPluginStartDependencies, + params, + config, + deps.dataSourceManagement + ); + }, + category: DEFAULT_APP_CATEGORIES.management, + }); + + if (deps.managementOverview) { + deps.managementOverview.register({ + id: PLUGIN_NAME, + title: 'Security', + order: 9050, + description: i18n.translate('security.securityDescription', { + defaultMessage: + 'Configure how users access data in OpenSearch with authentication, access control and audit logging.', + }), + }); + } + } - if (apiPermission) { core.application.register({ - id: PLUGIN_NAME, + id: APP_ID_CUSTOMERROR, title: 'Security', - order: 9050, + chromeless: true, + appRoute: CUSTOM_ERROR_PAGE_URI, mount: async (params: AppMountParameters) => { - const { renderApp } = await import('./apps/configuration/configuration-app'); - const [coreStart, depsStart] = await core.getStartServices(); - - // merge OpenSearchDashboards yml configuration - includeClusterPermissions(config.clusterPermissions.include); - includeIndexPermissions(config.indexPermissions.include); + const { renderPage } = await import('./apps/customerror/custom-error'); + const [coreStart] = await core.getStartServices(); + return renderPage(coreStart, params, config); + }, + }); + } - excludeFromDisabledTransportCategories(config.disabledTransportCategories.exclude); - excludeFromDisabledRestCategories(config.disabledRestCategories.exclude); + if (manageSessionEnabled) { + const accountInfo = (await fetchAccountInfoSafe(core.http))?.data; + const multitenancyEnabled = (await getDashboardsInfoSafe(core.http))?.multitenancy_enabled; + const isReadonly = accountInfo?.roles.some((role) => + (config.readonly_mode?.roles || DEFAULT_READONLY_ROLES).includes(role) + ); - return renderApp( - coreStart, - depsStart as SecurityPluginStartDependencies, - params, - config, - deps.dataSourceManagement - ); + core.application.register({ + id: APP_ID_LOGIN, + title: 'Security', + chromeless: true, + appRoute: LOGIN_PAGE_URI, + mount: async (params: AppMountParameters) => { + const { renderApp } = await import('./apps/login/login-app'); + // @ts-ignore depsStart not used. + const [coreStart, depsStart] = await core.getStartServices(); + return renderApp(coreStart, params, config); }, - category: DEFAULT_APP_CATEGORIES.management, }); - if (deps.managementOverview) { - deps.managementOverview.register({ - id: PLUGIN_NAME, - title: 'Security', - order: 9050, - description: i18n.translate('security.securityDescription', { - defaultMessage: - 'Configure how users access data in OpenSearch with authentication, access control and audit logging.', - }), - }); - } - } + core.application.registerAppUpdater( + new BehaviorSubject((app) => { + let shouldDisableForReadOnly = isReadonly && !APP_LIST_FOR_READONLY_ROLE.includes(app.id); + if (apiPermission !== undefined) { + shouldDisableForReadOnly = !apiPermission && shouldDisableForReadOnly; + } + if (shouldDisableForReadOnly) { + return { + status: AppStatus.inaccessible, + }; + } + }) + ); - core.application.register({ - id: APP_ID_LOGIN, - title: 'Security', - chromeless: true, - appRoute: LOGIN_PAGE_URI, - mount: async (params: AppMountParameters) => { - const { renderApp } = await import('./apps/login/login-app'); - // @ts-ignore depsStart not used. - const [coreStart, depsStart] = await core.getStartServices(); - return renderApp(coreStart, params, config); - }, - }); - - core.application.register({ - id: APP_ID_CUSTOMERROR, - title: 'Security', - chromeless: true, - appRoute: CUSTOM_ERROR_PAGE_URI, - mount: async (params: AppMountParameters) => { - const { renderPage } = await import('./apps/customerror/custom-error'); - const [coreStart] = await core.getStartServices(); - return renderPage(coreStart, params, config); - }, - }); - - core.application.registerAppUpdater( - new BehaviorSubject((app) => { - if (!apiPermission && isReadonly && !APP_LIST_FOR_READONLY_ROLE.includes(app.id)) { - return { - status: AppStatus.inaccessible, - }; + if ( + multitenancyEnabled && + config.multitenancy.enabled && + config.multitenancy.enable_aggregation_view + ) { + deps.savedObjectsManagement.columns.register( + (tenantColumn as unknown) as SavedObjectsManagementColumn + ); + if (!!accountInfo) { + const namespacesToRegister = getNamespacesToRegister(accountInfo); + deps.savedObjectsManagement.namespaces.registerAlias('Tenant'); + namespacesToRegister.forEach((ns) => { + deps.savedObjectsManagement.namespaces.register(ns as SavedObjectsManagementNamespace); + }); } - }) - ); - - if ( - multitenancyEnabled && - config.multitenancy.enabled && - config.multitenancy.enable_aggregation_view - ) { - deps.savedObjectsManagement.columns.register( - (tenantColumn as unknown) as SavedObjectsManagementColumn - ); - if (!!accountInfo) { - const namespacesToRegister = getNamespacesToRegister(accountInfo); - deps.savedObjectsManagement.namespaces.registerAlias('Tenant'); - namespacesToRegister.forEach((ns) => { - deps.savedObjectsManagement.namespaces.register(ns as SavedObjectsManagementNamespace); - }); } } @@ -195,17 +215,24 @@ export class SecurityPlugin public start(core: CoreStart, deps: SecurityPluginStartDependencies): SecurityPluginStart { const config = this.initializerContext.config.get(); - setupTopNavButton(core, config); + const manageSessionEnabled = + config.configuration?.session_management_enabled !== undefined + ? config.configuration?.session_management_enabled + : DEFAULT_MANAGE_SESSION_ENABLED; - if (config.ui.autologout) { - // logout the user when getting 401 unauthorized, e.g. when session timed out. - core.http.intercept({ - responseError: interceptError(config.auth.logout_url, window), - }); - } + if (manageSessionEnabled) { + setupTopNavButton(core, config); - if (config.multitenancy.enabled) { - addTenantToShareURL(core); + if (config.ui.autologout) { + // logout the user when getting 401 unauthorized, e.g. when session timed out. + core.http.intercept({ + responseError: interceptError(config.auth.logout_url, window), + }); + } + + if (config.multitenancy.enabled) { + addTenantToShareURL(core); + } } return {}; } diff --git a/public/types.ts b/public/types.ts index 96e587354..30a1a77cc 100644 --- a/public/types.ts +++ b/public/types.ts @@ -112,6 +112,11 @@ export interface ClientConfigType { anonymous_auth_enabled: boolean; logout_url: string; }; + configuration: { + enabled: boolean; + admin_pages_enabled: boolean; + session_management_enabled: boolean; + }; clusterPermissions: { include: string[]; }; diff --git a/server/index.ts b/server/index.ts index 68a20f533..c472ea70a 100644 --- a/server/index.ts +++ b/server/index.ts @@ -158,6 +158,8 @@ export const configSchema = schema.object({ }), configuration: schema.object({ enabled: schema.boolean({ defaultValue: true }), + admin_pages_enabled: schema.boolean({ defaultValue: true }), + session_management_enabled: schema.boolean({ defaultValue: true }), }), accountinfo: schema.object({ enabled: schema.boolean({ defaultValue: false }), @@ -299,6 +301,7 @@ export const config: PluginConfigDescriptor = { ui: true, multitenancy: true, readonly_mode: true, + configuration: true, clusterPermissions: true, indexPermissions: true, disabledTransportCategories: true, diff --git a/server/plugin.ts b/server/plugin.ts index a6d87bea1..bcaf8c241 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -97,6 +97,12 @@ export class SecurityPlugin implements Plugin = await core.http.createCookieSessionStorageFactory< - SecuritySessionCookie - >(getSecurityCookieOptions(config)); - // put logger into route handler context, so that we don't need to pass througth parameters core.http.registerRouteHandlerContext('security_plugin', (context, request) => { return { @@ -122,52 +124,54 @@ export class SecurityPlugin implements Plugin { - addTenantParameterToResolvedShortLink(request); - return toolkit.next(); - }); - } + if (config.configuration.session_management_enabled) { + const securitySessionStorageFactory: SessionStorageFactory = await core.http.createCookieSessionStorageFactory< + SecuritySessionCookie + >(getSecurityCookieOptions(config)); + + // setup auth + const auth: IAuthenticationType = await getAuthenticationHandler( + config.auth.type, + router, + config, + core, + esClient, + securitySessionStorageFactory, + this.logger + ); + core.http.registerAuth(auth.authHandler); + + /* Here we check if multitenancy is enabled to ensure if it is, we insert the tenant info (security_tenant) into the resolved, short URL so the page can correctly load with the right tenant information [Fix for issue 1203](https://github.com/opensearch-project/security-dashboards-plugin/issues/1203 */ + if (config.multitenancy?.enabled) { + core.http.registerOnPreResponse((request, preResponse, toolkit) => { + addTenantParameterToResolvedShortLink(request); + return toolkit.next(); + }); + } - // Register server side APIs - defineRoutes(router, dataSourceEnabled); - defineAuthTypeRoutes(router, config); + // set up multi-tenant routes + if (config.multitenancy?.enabled) { + setupMultitenantRoutes(router, securitySessionStorageFactory, this.securityClient); + } - // set up multi-tenant routes - if (config.multitenancy?.enabled) { - setupMultitenantRoutes(router, securitySessionStorageFactory, this.securityClient); - } + if (config.multitenancy.enabled && config.multitenancy.enable_aggregation_view) { + core.savedObjects.addClientWrapper( + 2, + 'security-saved-object-client-wrapper', + this.savedObjectClientWrapper.wrapperFactory + ); + } - if (config.multitenancy.enabled && config.multitenancy.enable_aggregation_view) { - core.savedObjects.addClientWrapper( - 2, - 'security-saved-object-client-wrapper', - this.savedObjectClientWrapper.wrapperFactory + const service = new ReadonlyService( + this.logger, + this.securityClient, + auth, + securitySessionStorageFactory, + config ); - } - const service = new ReadonlyService( - this.logger, - this.securityClient, - auth, - securitySessionStorageFactory, - config - ); - - core.security.registerReadonlyService(service); + core.security.registerReadonlyService(service); + } return { config$, diff --git a/test/cypress/e2e/multi-datasources/multi_datasources_disabled.spec.js b/test/cypress/e2e/multi-datasources/multi_datasources_disabled.spec.js index b26079fe7..694a4721f 100644 --- a/test/cypress/e2e/multi-datasources/multi_datasources_disabled.spec.js +++ b/test/cypress/e2e/multi-datasources/multi_datasources_disabled.spec.js @@ -27,7 +27,7 @@ describe('Multi-datasources disabled', () => { it('Checks Get Started Tab', () => { // Remote cluster purge cache - cy.visit(`http://localhost:5601/app/security-dashboards-plugin#/getstarted`); + cy.visit(`http://localhost:5601/app/security-admin-dashboards-plugin#/getstarted`); cy.contains('h1', 'Get started'); cy.get('[data-test-subj="dataSourceSelectableButton"]').should('not.exist'); diff --git a/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js b/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js index 2a1a0c4f7..841aef666 100644 --- a/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js +++ b/test/cypress/e2e/multi-datasources/multi_datasources_enabled.spec.js @@ -93,7 +93,7 @@ describe('Multi-datasources enabled', () => { it('Checks Get Started Tab', () => { // Remote cluster purge cache cy.visit( - `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/getstarted` + `http://localhost:5601/app/security-admin-dashboards-plugin${externalDataSourceUrl}#/getstarted` ); cy.contains('h1', 'Get started'); @@ -106,7 +106,9 @@ describe('Multi-datasources enabled', () => { }); it('Checks Auth Tab', () => { - cy.visit(`http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/auth`); + cy.visit( + `http://localhost:5601/app/security-admin-dashboards-plugin${externalDataSourceUrl}#/auth` + ); cy.get('.panel-header-count').first().invoke('text').should('contain', '(1)'); }); @@ -125,7 +127,7 @@ describe('Multi-datasources enabled', () => { }, }).then(() => { cy.visit( - `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/users` + `http://localhost:5601/app/security-admin-dashboards-plugin${externalDataSourceUrl}#/users` ); cy.get('[data-test-subj="tableHeaderCell_username_0"]').click(); @@ -145,7 +147,7 @@ describe('Multi-datasources enabled', () => { }, }).then(() => { cy.visit( - `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/permissions` + `http://localhost:5601/app/security-admin-dashboards-plugin${externalDataSourceUrl}#/permissions` ); // Permission exists on the remote data source @@ -156,7 +158,9 @@ describe('Multi-datasources enabled', () => { it('Checks Tenancy Tab', () => { // Datasource is locked to local cluster for tenancy tab - cy.visit(`http://localhost:5601/app/security-dashboards-plugin${localDataSourceUrl}#/tenants`); + cy.visit( + `http://localhost:5601/app/security-admin-dashboards-plugin${localDataSourceUrl}#/tenants` + ); cy.contains('h1', 'Dashboards multi-tenancy'); cy.get('[data-test-subj="dataSourceViewButton"]').should('contain', 'Local cluster'); @@ -165,7 +169,7 @@ describe('Multi-datasources enabled', () => { it('Checks Service Accounts Tab', () => { // Datasource is locked to local cluster for service accounts tab cy.visit( - `http://localhost:5601/app/security-dashboards-plugin${localDataSourceUrl}#/serviceAccounts` + `http://localhost:5601/app/security-admin-dashboards-plugin${localDataSourceUrl}#/serviceAccounts` ); cy.get('[data-test-subj="dataSourceViewButton"]').should('contain', 'Local cluster'); @@ -207,7 +211,7 @@ describe('Multi-datasources enabled', () => { }, }).then(() => { cy.visit( - `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/auditLogging` + `http://localhost:5601/app/security-admin-dashboards-plugin${externalDataSourceUrl}#/auditLogging` ); cy.get('[class="euiSwitch__label"]').should('contain', 'Disabled'); }); @@ -227,7 +231,7 @@ describe('Multi-datasources enabled', () => { }, }).then(() => { cy.visit( - `http://localhost:5601/app/security-dashboards-plugin${externalDataSourceUrl}#/roles` + `http://localhost:5601/app/security-admin-dashboards-plugin${externalDataSourceUrl}#/roles` ); cy.get('[data-test-subj="dataSourceSelectableButton"]').should('contain', '9202'); diff --git a/test/cypress/e2e/oidc/oidc_auth_test.spec.js b/test/cypress/e2e/oidc/oidc_auth_test.spec.js index 2228d2fbf..893c743e0 100644 --- a/test/cypress/e2e/oidc/oidc_auth_test.spec.js +++ b/test/cypress/e2e/oidc/oidc_auth_test.spec.js @@ -69,7 +69,7 @@ describe('Log in via OIDC', () => { }); it('Login to Dashboard with Hash', () => { - const urlWithHash = `http://localhost:5601${basePath}/app/security-dashboards-plugin#/getstarted`; + const urlWithHash = `http://localhost:5601${basePath}/app/security-admin-dashboards-plugin#/getstarted`; cy.visit(urlWithHash, { failOnStatusCode: false, diff --git a/test/cypress/e2e/saml/saml_auth_test.spec.js b/test/cypress/e2e/saml/saml_auth_test.spec.js index 34f58da2b..923771a2d 100644 --- a/test/cypress/e2e/saml/saml_auth_test.spec.js +++ b/test/cypress/e2e/saml/saml_auth_test.spec.js @@ -82,7 +82,7 @@ describe('Log in via SAML', () => { localStorage.setItem('opendistro::security::tenant::saved', '"__user__"'); localStorage.setItem('home:newThemeModal:show', 'false'); - const urlWithHash = `http://localhost:5601${basePath}/app/security-dashboards-plugin#/getstarted`; + const urlWithHash = `http://localhost:5601${basePath}/app/security-admin-dashboards-plugin#/getstarted`; cy.visit(urlWithHash, { failOnStatusCode: false,