diff --git a/.github/actions/run-cypress-tests/action.yml b/.github/actions/run-cypress-tests/action.yml index eae07eac8..d77adf5a2 100644 --- a/.github/actions/run-cypress-tests/action.yml +++ b/.github/actions/run-cypress-tests/action.yml @@ -11,6 +11,10 @@ inputs: yarn_command: description: 'The yarn command to start running cypress tests' required: true + osd_base_path: + description: 'The base path for OpenSearch Dashboards' + required: false + default: '' runs: using: "composite" @@ -56,14 +60,22 @@ runs: if: ${{ runner.os == 'Linux' }} run: | cd ./OpenSearch-Dashboards/plugins/security-dashboards-plugin - yarn runIdp + if [ -z "${{ inputs.osd_base_path }}" ]; then + yarn runIdp & + else + yarn runIdp --basePath ${{ inputs.osd_base_path }} & + fi shell: bash - name: Run OpenSearch Dashboards with provided configuration if: ${{ runner.os == 'Linux' }} run: | cd ./OpenSearch-Dashboards - nohup yarn start --no-base-path --no-watch --csp.warnLegacyBrowsers=false | tee dashboard.log & + if [ -z "${{ inputs.osd_base_path }}" ]; then + nohup yarn start --no-base-path --no-watch --csp.warnLegacyBrowsers=false | tee dashboard.log & + else + nohup yarn start --no-watch --csp.warnLegacyBrowsers=false | tee dashboard.log & + fi shell: bash # Check if OSD is ready with a max timeout of 600 seconds diff --git a/.github/workflows/cypress-test-multiauth-e2e.yml b/.github/workflows/cypress-test-multiauth-e2e.yml index 3b9827b0f..bb7985f9b 100644 --- a/.github/workflows/cypress-test-multiauth-e2e.yml +++ b/.github/workflows/cypress-test-multiauth-e2e.yml @@ -17,6 +17,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] + basePath: [ "", "/osd" ] runs-on: ${{ matrix.os }} steps: @@ -61,7 +62,7 @@ jobs: metadata_url: http://localhost:7000/metadata sp: entity_id: https://localhost:9200 - kibana_url: http://localhost:5601 + kibana_url: http://localhost:5601${{ matrix.basePath }} exchange_key: 6aff3042-1327-4f3d-82f0-40a157ac4464 authentication_backend: type: noop @@ -82,14 +83,30 @@ jobs: opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"] opensearch_security.readonly_mode.roles: ["kibana_read_only"] opensearch_security.cookie.secure: false - server.xsrf.allowlist: ["/_plugins/_security/api/authtoken", "/_opendistro/_security/api/authtoken", "/_opendistro/_security/saml/acs", "/_opendistro/_security/saml/acs/idpinitiated", "/_opendistro/_security/saml/logout"] + server.xsrf.allowlist: ["/_opendistro/_security/saml/acs", "/_opendistro/_security/saml/acs/idpinitiated", "/_opendistro/_security/saml/logout"] opensearch_security.auth.type: ["basicauth","saml"] opensearch_security.auth.multiple_auth_enabled: true opensearch_security.auth.anonymous_auth_enabled: false home.disableWelcomeScreen: true EOT + - name: Run OSD with basePath + if: ${{ matrix.basePath != '' }} + run: | + echo "server.basePath: \"${{ matrix.basePath }}\"" >> opensearch_dashboards_multiauth.yml + echo "server.rewriteBasePath: true" >> opensearch_dashboards_multiauth.yml + + - name: Run Cypress Tests with basePath + if: ${{ matrix.basePath != '' }} + uses: ./.github/actions/run-cypress-tests + with: + security_config_file: config_multiauth.yml + dashboards_config_file: opensearch_dashboards_multiauth.yml + yarn_command: 'yarn cypress:run --browser chrome --headless --env loginMethod=saml_multiauth,basePath=${{ matrix.basePath }} --spec "test/cypress/e2e/saml/*.js"' + osd_base_path: ${{ matrix.basePath }} + - name: Run Cypress Tests + if: ${{ matrix.basePath == '' }} uses: ./.github/actions/run-cypress-tests with: security_config_file: config_multiauth.yml diff --git a/.github/workflows/cypress-test-oidc-e2e.yml b/.github/workflows/cypress-test-oidc-e2e.yml index 59bfe08e4..11e51d390 100644 --- a/.github/workflows/cypress-test-oidc-e2e.yml +++ b/.github/workflows/cypress-test-oidc-e2e.yml @@ -21,6 +21,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] + basePath: [ "", "/osd" ] runs-on: ${{ matrix.os }} steps: @@ -62,7 +63,7 @@ jobs: chmod +x kcadm.sh echo "Creating client" ./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password admin - CID=$(./kcadm.sh create clients -r master -s clientId=opensearch -s secret="${{ env.TEST_KEYCLOAK_CLIENT_SECRET }}" -s 'attributes."access.token.lifespan"=60' -s 'redirectUris=["http://localhost:5603/auth/openid/login", "http://localhost:5601", "http://localhost:5601/auth/openid/login"]' -i) + CID=$(./kcadm.sh create clients -r master -s clientId=opensearch -s secret="${{ env.TEST_KEYCLOAK_CLIENT_SECRET }}" -s 'attributes."access.token.lifespan"=60' -s 'redirectUris=["http://localhost:5603${{ matrix.basePath }}/auth/openid/login", "http://localhost:5601${{ matrix.basePath }}", "http://localhost:5601${{ matrix.basePath }}/auth/openid/login"]' -i) ./kcadm.sh get clients/$CID/installation/providers/keycloak-oidc-keycloak-json > tmp echo "Getting client secret for dashboards configuration purpose" CLIENT_SECRET=$(grep -o '"secret" : "[^"]*' tmp | grep -o '[^"]*$') @@ -129,13 +130,28 @@ jobs: opensearch_security.openid.connect_url: "http://127.0.0.1:8080/auth/realms/master/.well-known/openid-configuration" opensearch_security.openid.client_id: "opensearch" opensearch_security.openid.client_secret: "${{ env.TEST_KEYCLOAK_CLIENT_SECRET }}" - opensearch_security.auth.type: ["openid"] - opensearch_security.auth.multiple_auth_enabled: true - opensearch_security.ui.openid.login.buttonname: "OIDC" + opensearch_security.openid.base_redirect_url: http://localhost:5601${{ matrix.basePath }} + opensearch_security.auth.type: "openid" home.disableWelcomeScreen: true EOT + - name: Run OSD with basePath + if: ${{ matrix.basePath != '' }} + run: | + echo "server.basePath: \"${{ matrix.basePath }}\"" >> opensearch_dashboards_openid.yml + echo "server.rewriteBasePath: true" >> opensearch_dashboards_openid.yml + + - name: Run Cypress Tests with basePath + if: ${{ matrix.basePath != '' }} + uses: ./.github/actions/run-cypress-tests + with: + security_config_file: config_openid.yml + dashboards_config_file: opensearch_dashboards_openid.yml + yarn_command: 'yarn cypress:run --browser chrome --headless --spec "test/cypress/e2e/oidc/*.js" --env basePath=${{ matrix.basePath }}' + osd_base_path: ${{ matrix.basePath }} + - name: Run Cypress Tests + if: ${{ matrix.basePath == '' }} uses: ./.github/actions/run-cypress-tests with: security_config_file: config_openid.yml diff --git a/.github/workflows/cypress-test-saml-e2e.yml b/.github/workflows/cypress-test-saml-e2e.yml index b5b697ba4..3025c2cde 100644 --- a/.github/workflows/cypress-test-saml-e2e.yml +++ b/.github/workflows/cypress-test-saml-e2e.yml @@ -17,6 +17,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] + basePath: [ "", "/osd" ] runs-on: ${{ matrix.os }} steps: @@ -61,7 +62,7 @@ jobs: metadata_url: http://localhost:7000/metadata sp: entity_id: https://localhost:9200 - kibana_url: http://localhost:5601 + kibana_url: http://localhost:5601${{ matrix.basePath }} exchange_key: 6aff3042-1327-4f3d-82f0-40a157ac4464 authentication_backend: type: noop @@ -82,14 +83,30 @@ jobs: opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"] opensearch_security.readonly_mode.roles: ["kibana_read_only"] opensearch_security.cookie.secure: false - server.xsrf.allowlist: ["/_plugins/_security/api/authtoken", "/_opendistro/_security/api/authtoken", "/_opendistro/_security/saml/acs", "/_opendistro/_security/saml/acs/idpinitiated", "/_opendistro/_security/saml/logout"] + server.xsrf.allowlist: ["/_opendistro/_security/saml/acs", "/_opendistro/_security/saml/acs/idpinitiated", "/_opendistro/_security/saml/logout"] opensearch_security.auth.type: ["saml"] opensearch_security.auth.multiple_auth_enabled: true opensearch_security.auth.anonymous_auth_enabled: false home.disableWelcomeScreen: true EOT + - name: Run OSD with basePath + if: ${{ matrix.basePath != '' }} + run: | + echo "server.basePath: \"${{ matrix.basePath }}\"" >> opensearch_dashboards_saml.yml + echo "server.rewriteBasePath: true" >> opensearch_dashboards_saml.yml + + - name: Run Cypress Tests with basePath + if: ${{ matrix.basePath != '' }} + uses: ./.github/actions/run-cypress-tests + with: + security_config_file: config_saml.yml + dashboards_config_file: opensearch_dashboards_saml.yml + yarn_command: 'yarn cypress:run --browser chrome --headless --spec "test/cypress/e2e/saml/*.js" --env basePath=${{ matrix.basePath }}' + osd_base_path: ${{ matrix.basePath }} + - name: Run Cypress Tests + if: ${{ matrix.basePath == '' }} uses: ./.github/actions/run-cypress-tests with: security_config_file: config_saml.yml diff --git a/package.json b/package.json index 944c3f426..c8b786ad8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "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 &", + "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" @@ -30,14 +30,15 @@ "@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", - "eslint-plugin-cypress": "^2.8.1", - "eslint-plugin-unused-imports": "3.1.0", - "husky": "^8.0.0" + "typescript": "4.0.2" }, "dependencies": { "@hapi/cryptiles": "5.0.0", diff --git a/public/apps/login/login-page.tsx b/public/apps/login/login-page.tsx index 1e5f43dd8..abebf304b 100644 --- a/public/apps/login/login-page.tsx +++ b/public/apps/login/login-page.tsx @@ -84,7 +84,7 @@ export function extractNextUrlFromWindowLocation(): string { const urlParams = new URLSearchParams(window.location.search); let nextUrl = urlParams.get('nextUrl'); if (!nextUrl || nextUrl.toLowerCase().includes('//')) { - nextUrl = encodeURIComponent('/'); + return ''; } else { nextUrl = encodeURIComponent(nextUrl); const hash = window.location.hash || ''; diff --git a/public/apps/login/test/__snapshots__/login-page.test.tsx.snap b/public/apps/login/test/__snapshots__/login-page.test.tsx.snap index de432dcca..f04a02a1d 100644 --- a/public/apps/login/test/__snapshots__/login-page.test.tsx.snap +++ b/public/apps/login/test/__snapshots__/login-page.test.tsx.snap @@ -121,7 +121,7 @@ exports[`Login page renders renders with config value for multiauth 1`] = ` aria-label="openid_login_button" className="test-btn-style" data-test-subj="submit" - href="/app/opensearch-dashboards/auth/openid/captureUrlFragment?nextUrl=%2F" + href="/app/opensearch-dashboards/auth/openid/captureUrlFragment" iconType="http://localhost:5601/images/test.png" size="s" type="prime" @@ -141,7 +141,7 @@ exports[`Login page renders renders with config value for multiauth 1`] = ` aria-label="saml_login_button" className="test-btn-style" data-test-subj="submit" - href="/app/opensearch-dashboards/auth/saml/captureUrlFragment?nextUrl=%2F" + href="/app/opensearch-dashboards/auth/saml/captureUrlFragment" iconType="http://localhost:5601/images/test.png" size="s" type="prime" @@ -292,7 +292,7 @@ exports[`Login page renders renders with config value for multiauth with anonymo aria-label="openid_login_button" className="test-btn-style" data-test-subj="submit" - href="/app/opensearch-dashboards/auth/openid/captureUrlFragment?nextUrl=%2F" + href="/app/opensearch-dashboards/auth/openid/captureUrlFragment" iconType="http://localhost:5601/images/test.png" size="s" type="prime" @@ -312,7 +312,7 @@ exports[`Login page renders renders with config value for multiauth with anonymo aria-label="saml_login_button" className="test-btn-style" data-test-subj="submit" - href="/app/opensearch-dashboards/auth/saml/captureUrlFragment?nextUrl=%2F" + href="/app/opensearch-dashboards/auth/saml/captureUrlFragment" iconType="http://localhost:5601/images/test.png" size="s" type="prime" diff --git a/public/apps/login/test/login-page.test.tsx b/public/apps/login/test/login-page.test.tsx index d764d56fa..167c9f148 100644 --- a/public/apps/login/test/login-page.test.tsx +++ b/public/apps/login/test/login-page.test.tsx @@ -91,7 +91,7 @@ describe('test extractNextUrlFromWindowLocation', () => { const originalLocation = window.location; delete window.location; window.location = new URL('http://localhost:5601/app/home'); - expect(extractNextUrlFromWindowLocation()).toEqual('?nextUrl=%2F'); + expect(extractNextUrlFromWindowLocation()).toEqual(''); }); }); diff --git a/server/auth/types/openid/routes.ts b/server/auth/types/openid/routes.ts index c23e26b1f..51ac1a85d 100644 --- a/server/auth/types/openid/routes.ts +++ b/server/auth/types/openid/routes.ts @@ -336,8 +336,10 @@ export class OpenIdAuthRoutes { } let params = new URLSearchParams(window.location.search); let nextUrl = params.get("nextUrl"); - finalUrl = "login?nextUrl=" + encodeURIComponent(nextUrl); - finalUrl += "&redirectHash=" + encodeURIComponent(redirectHash); + finalUrl = "login?redirectHash=" + encodeURIComponent(redirectHash); + if (!!nextUrl) { + finalUrl += "&nextUrl=" + encodeURIComponent(nextUrl); + } window.location.replace(finalUrl); `, }); diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index d14d0711b..52af443af 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -314,8 +314,10 @@ export class SamlAuthRoutes { } let params = new URLSearchParams(window.location.search); let nextUrl = params.get("nextUrl"); - finalUrl = "login?nextUrl=" + encodeURIComponent(nextUrl); - finalUrl += "&redirectHash=" + encodeURIComponent(redirectHash); + finalUrl = "login?redirectHash=" + encodeURIComponent(redirectHash); + if (!!nextUrl) { + finalUrl += "&nextUrl=" + encodeURIComponent(nextUrl); + } window.location.replace(finalUrl); `, }); diff --git a/server/session/security_cookie.ts b/server/session/security_cookie.ts index 4b0c52b89..d2a02ff98 100644 --- a/server/session/security_cookie.ts +++ b/server/session/security_cookie.ts @@ -59,7 +59,7 @@ export function getSecurityCookieOptions( // TODO: with setting redirect attributes to support OIDC and SAML, // we need to do additional cookie validation in AuthenticationHandlers. // if SAML fields present - if (sessionStorage.saml && sessionStorage.saml.requestId && sessionStorage.saml.nextUrl) { + if (sessionStorage.saml && sessionStorage.saml.requestId) { return { isValid: true, path: '/' }; } diff --git a/test/cypress/e2e/oidc/oidc_auth_test.spec.js b/test/cypress/e2e/oidc/oidc_auth_test.spec.js index 978cd0ec3..2228d2fbf 100644 --- a/test/cypress/e2e/oidc/oidc_auth_test.spec.js +++ b/test/cypress/e2e/oidc/oidc_auth_test.spec.js @@ -18,6 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +const basePath = Cypress.env('basePath') || ''; + describe('Log in via OIDC', () => { afterEach(() => { cy.clearCookies(); @@ -37,7 +39,7 @@ describe('Log in via OIDC', () => { }; it('Login to app/opensearch_dashboards_overview#/ when OIDC is enabled', () => { - cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + cy.visit(`http://localhost:5601${basePath}/app/opensearch_dashboards_overview`, { failOnStatusCode: false, }); @@ -52,24 +54,22 @@ describe('Log in via OIDC', () => { }); it('Login to app/dev_tools#/console when OIDC is enabled', () => { - cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + cy.visit(`http://localhost:5601${basePath}/app/dev_tools#/console`, { failOnStatusCode: false, }); kcLogin(); + cy.getCookie('security_authentication').should('exist'); + localStorage.setItem('opendistro::security::tenant::saved', '""'); localStorage.setItem('home:newThemeModal:show', 'false'); - cy.visit('http://localhost:5601/app/dev_tools#/console'); - cy.get('a[data-test-subj="breadcrumb first last"]').contains('Dev Tools').should('be.visible'); - - cy.getCookie('security_authentication').should('exist'); }); it('Login to Dashboard with Hash', () => { - const urlWithHash = `http://localhost:5601/app/security-dashboards-plugin#/getstarted`; + const urlWithHash = `http://localhost:5601${basePath}/app/security-dashboards-plugin#/getstarted`; cy.visit(urlWithHash, { failOnStatusCode: false, @@ -92,7 +92,7 @@ describe('Log in via OIDC', () => { }); it('Tenancy persisted after logout in OIDC', () => { - cy.visit('http://localhost:5601/app/opensearch_dashboards_overview#/', { + cy.visit(`http://localhost:5601${basePath}/app/opensearch_dashboards_overview#/`, { failOnStatusCode: false, }); @@ -115,8 +115,12 @@ describe('Log in via OIDC', () => { cy.get('button[id="user-icon-btn"]').click(); + cy.intercept('GET', `${basePath}/auth/openid/logout`).as('openidLogout'); + cy.get('button[data-test-subj^="log-out-"]').click(); + cy.wait('@openidLogout').then(() => {}); + kcLogin(); cy.get('#user-icon-btn').should('be.visible'); diff --git a/test/cypress/e2e/saml/saml_auth_test.spec.js b/test/cypress/e2e/saml/saml_auth_test.spec.js index b8f6a134f..34f58da2b 100644 --- a/test/cypress/e2e/saml/saml_auth_test.spec.js +++ b/test/cypress/e2e/saml/saml_auth_test.spec.js @@ -22,12 +22,14 @@ import { ALL_ACCESS_ROLE, SHORTEN_URL_DATA } from '../../support/constants'; import samlUserRoleMapping from '../../fixtures/saml/samlUserRoleMappiing.json'; +const basePath = Cypress.env('basePath') || ''; + before(() => { cy.intercept('https://localhost:9200'); // Avoid Cypress lock onto the ipv4 range, so fake `visit()` before `request()`. // See: https://github.com/cypress-io/cypress/issues/25397#issuecomment-1402556488 - cy.visit('http://localhost:5601'); + cy.visit(`http://localhost:5601${basePath}`); cy.createRoleMapping(ALL_ACCESS_ROLE, samlUserRoleMapping); cy.clearCookies(); @@ -52,7 +54,7 @@ describe('Log in via SAML', () => { localStorage.setItem('opendistro::security::tenant::saved', '"__user__"'); localStorage.setItem('home:newThemeModal:show', 'false'); - cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + cy.visit(`http://localhost:5601${basePath}/app/opensearch_dashboards_overview`, { failOnStatusCode: false, }); @@ -66,7 +68,7 @@ describe('Log in via SAML', () => { localStorage.setItem('opendistro::security::tenant::saved', '"__user__"'); localStorage.setItem('home:newThemeModal:show', 'false'); - cy.visit('http://localhost:5601/app/dev_tools#/console', { + cy.visit(`http://localhost:5601${basePath}/app/dev_tools#/console`, { failOnStatusCode: false, }); @@ -80,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/app/security-dashboards-plugin#/getstarted`; + const urlWithHash = `http://localhost:5601${basePath}/app/security-dashboards-plugin#/getstarted`; cy.visit(urlWithHash, { failOnStatusCode: false, @@ -95,7 +97,7 @@ describe('Log in via SAML', () => { it('Tenancy persisted after logout in SAML', () => { localStorage.setItem('home:newThemeModal:show', 'false'); - cy.visit('http://localhost:5601/app/opensearch_dashboards_overview', { + cy.visit(`http://localhost:5601${basePath}/app/opensearch_dashboards_overview`, { failOnStatusCode: false, }); @@ -128,7 +130,7 @@ describe('Log in via SAML', () => { // We need to explicitly clear cookies, // since the Shorten URL api is return's set-cookie header for admin user. cy.clearCookies().then(() => { - const gotoUrl = `http://localhost:5601/goto/${response.urlId}?security_tenant=global`; + const gotoUrl = `http://localhost:5601${basePath}/goto/${response.urlId}?security_tenant=global`; cy.visit(gotoUrl); samlLogin(); cy.getCookie('security_authentication').should('exist'); diff --git a/test/cypress/support/constants.js b/test/cypress/support/constants.js index b357659c6..a9ce18f3f 100644 --- a/test/cypress/support/constants.js +++ b/test/cypress/support/constants.js @@ -32,6 +32,8 @@ export const ADMIN_AUTH = { password: Cypress.env('adminPassword'), }; +const basePath = Cypress.env('basePath') || ''; + //Security API Constants export const SEC_API_PREFIX = '/_plugins/_security/api'; export const SEC_API = { @@ -41,6 +43,6 @@ export const SEC_API = { ROLE_MAPPING_BASE: `${SEC_API_PREFIX}/rolesmapping`, }; export const DASHBOARDS_API = { - SHORTEN_URL: '/api/shorten_url', + SHORTEN_URL: `${basePath}/api/shorten_url`, }; -export const SHORTEN_URL_DATA = { url: '/app/home#/tutorial_directory' }; +export const SHORTEN_URL_DATA = { url: `/app/home#/tutorial_directory` }; diff --git a/test/jest_integration/runIdpServer.js b/test/jest_integration/runIdpServer.js index 35533ae6c..13149dbd5 100644 --- a/test/jest_integration/runIdpServer.js +++ b/test/jest_integration/runIdpServer.js @@ -17,15 +17,21 @@ const { runServer } = require('saml-idp'); const { generate } = require('selfsigned'); +const minimist = require('minimist'); + const pems = generate(null, { keySize: 2048, clientCertificateCN: '/C=US/ST=California/L=San Francisco/O=JankyCo/CN=Test Identity Provider', days: 7300, }); +const argv = minimist(process.argv.slice(2), { + default: { basePath: '' }, +}); + // Create certificate pair on the fly and pass it to runServer runServer({ - acsUrl: 'http://localhost:5601/_opendistro/_security/saml/acs', + acsUrl: `http://localhost:5601${argv.basePath}/_opendistro/_security/saml/acs`, audience: 'https://localhost:9200', cert: pems.cert, key: pems.private.toString().replace(/\r\n/, '\n'),