From 9e4f59829bd043118cb6dcc4bb04284e875b0ecc Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Mon, 16 Oct 2023 12:00:52 -0500 Subject: [PATCH 1/8] Show error when chroot_namespace defined but does not exist --- ui/app/adapters/cluster.js | 8 +++++++ ui/app/templates/error.hbs | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 ui/app/templates/error.hbs diff --git a/ui/app/adapters/cluster.js b/ui/app/adapters/cluster.js index de173feb8897..b3ccec85b80a 100644 --- a/ui/app/adapters/cluster.js +++ b/ui/app/adapters/cluster.js @@ -80,6 +80,14 @@ export default ApplicationAdapter.extend({ performancestandbycode: 200, }, unauthenticated: true, + }).catch((e) => { + if (e.errors?.includes('invalid namespace specified for chroot_namespace')) { + // This error will occur if the call is made with chroot_namespace set on config + // but the specified namespace does not exist. In this case we should show the error. + e.message = + 'chroot_namespace has been set but the namespace does not exist. Please create the namespace and then refresh the page.'; + } + throw e; }); }, diff --git a/ui/app/templates/error.hbs b/ui/app/templates/error.hbs new file mode 100644 index 000000000000..9448637df46b --- /dev/null +++ b/ui/app/templates/error.hbs @@ -0,0 +1,46 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +~}} + +
+
+
+
+ +
+
+
+
+ +
+
+

+ {{#if (eq this.model.httpStatus 403)}} + Not authorized + {{else if (eq this.model.httpStatus 404)}} + Page not found + {{else}} + Error + {{/if}} +

+

Error {{this.model.httpStatus}}

+
+
+ +

+ {{this.model.message}} + {{join ". " this.model.errors}} +

+ +
+ + + Go home + + + Learn more + +
+
+
\ No newline at end of file From 46bdcd78f7cbfdd3d5379e5df8aafa75d3c30524 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Mon, 16 Oct 2023 16:19:11 -0500 Subject: [PATCH 2/8] add has_chroot_namespace attr to cluster model, set manually when health check fails --- ui/app/adapters/cluster.js | 4 +++- ui/app/models/cluster.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/app/adapters/cluster.js b/ui/app/adapters/cluster.js index b3ccec85b80a..09d9d5f2064d 100644 --- a/ui/app/adapters/cluster.js +++ b/ui/app/adapters/cluster.js @@ -86,8 +86,10 @@ export default ApplicationAdapter.extend({ // but the specified namespace does not exist. In this case we should show the error. e.message = 'chroot_namespace has been set but the namespace does not exist. Please create the namespace and then refresh the page.'; + throw e; } - throw e; + // otherwise, sys/health will fail when chroot set because it's allowed in root namespace only + return { has_chroot_namespace: true }; }); }, diff --git a/ui/app/models/cluster.js b/ui/app/models/cluster.js index 543ed7bf0a60..6ac7f90af367 100644 --- a/ui/app/models/cluster.js +++ b/ui/app/models/cluster.js @@ -16,6 +16,8 @@ export default class ClusterModel extends Model { @attr('boolean') standby; @attr('string') type; @attr('object') license; + // manually set on response when sys/health failure + @attr('boolean') hasChrootNamespace; /* Licensing concerns */ get licenseExpiry() { From 7c3b83ae2d0dddacad7992c47a26c15ad7101b03 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Mon, 16 Oct 2023 16:23:53 -0500 Subject: [PATCH 3/8] Use seal-status for version --- ui/app/models/node.js | 7 +++---- ui/app/services/version.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/app/models/node.js b/ui/app/models/node.js index 520368c72d01..003d3f7ab99c 100644 --- a/ui/app/models/node.js +++ b/ui/app/models/node.js @@ -9,17 +9,16 @@ import { alias, and, equal } from '@ember/object/computed'; export default Model.extend({ name: attr('string'), // https://developer.hashicorp.com/vault/api-docs/system/health - initialized: attr('boolean'), - sealed: attr('boolean'), - isSealed: alias('sealed'), standby: attr('boolean'), isActive: equal('standby', false), - clusterName: attr('string'), clusterId: attr('string'), isLeader: and('initialized', 'isActive'), // https://developer.hashicorp.com/vault/api-docs/system/seal-status + initialized: attr('boolean'), + sealed: attr('boolean'), + isSealed: alias('sealed'), // The "t" parameter is the threshold, and "n" is the number of shares. t: attr('number'), n: attr('number'), diff --git a/ui/app/services/version.js b/ui/app/services/version.js index fe1ec0aae891..cf99b759c7a8 100644 --- a/ui/app/services/version.js +++ b/ui/app/services/version.js @@ -44,7 +44,7 @@ export default class VersionService extends Service { @task *getVersion() { if (this.version) return; - const response = yield this.store.adapterFor('cluster').health(); + const response = yield this.store.adapterFor('cluster').sealStatus(); this.version = response.version; return; } From d9f01dcf9758efc740d59050238ff0a93de7049a Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Mon, 16 Oct 2023 16:39:11 -0500 Subject: [PATCH 4/8] Hide items that aren't accessible from child namespaces when chroot --- ui/app/routes/vault/cluster/dashboard.js | 16 +++++++++------- ui/app/templates/vault/cluster/dashboard.hbs | 12 ++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ui/app/routes/vault/cluster/dashboard.js b/ui/app/routes/vault/cluster/dashboard.js index b50a68c1dbef..37b2cb88b7de 100644 --- a/ui/app/routes/vault/cluster/dashboard.js +++ b/ui/app/routes/vault/cluster/dashboard.js @@ -29,18 +29,20 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout model() { const clusterModel = this.modelFor('vault.cluster'); - const replication = { - dr: clusterModel.dr, - performance: clusterModel.performance, - }; - + const hasChroot = clusterModel?.hasChrootNamespace; + const replication = hasChroot + ? null + : { + dr: clusterModel.dr, + performance: clusterModel.performance, + }; return hash({ replication, secretsEngines: this.store.query('secret-engine', {}), license: this.store.queryRecord('license', {}).catch(() => null), - isRootNamespace: this.namespace.inRootNamespace, + isRootNamespace: this.namespace.inRootNamespace && !hasChroot, version: this.version, - vaultConfiguration: this.getVaultConfiguration(), + vaultConfiguration: hasChroot ? null : this.getVaultConfiguration(), }); } diff --git a/ui/app/templates/vault/cluster/dashboard.hbs b/ui/app/templates/vault/cluster/dashboard.hbs index fad7cd54d79e..b7f212674a0d 100644 --- a/ui/app/templates/vault/cluster/dashboard.hbs +++ b/ui/app/templates/vault/cluster/dashboard.hbs @@ -4,12 +4,12 @@ ~}} \ No newline at end of file From 61e3f18f3343162177cdac1afbf06f1aba4aa03d Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Wed, 1 Nov 2023 10:34:30 -0500 Subject: [PATCH 5/8] Comments --- ui/app/adapters/cluster.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/adapters/cluster.js b/ui/app/adapters/cluster.js index 09d9d5f2064d..c329f313a7be 100644 --- a/ui/app/adapters/cluster.js +++ b/ui/app/adapters/cluster.js @@ -88,7 +88,9 @@ export default ApplicationAdapter.extend({ 'chroot_namespace has been set but the namespace does not exist. Please create the namespace and then refresh the page.'; throw e; } - // otherwise, sys/health will fail when chroot set because it's allowed in root namespace only + // otherwise, sys/health will only fail when chroot set + // because it's allowed in root namespace only and + // configured to return a 200 response in other cases return { has_chroot_namespace: true }; }); }, From 69cf55d3659509e31634b7950485d4ee7f7862e2 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 2 Nov 2023 09:33:18 -0500 Subject: [PATCH 6/8] add tests and move unset chroot error handling --- ui/app/adapters/cluster.js | 13 ++------- ui/mirage/handlers/chroot-namespace.js | 15 ++++++++++ ui/mirage/handlers/index.js | 2 ++ ui/package.json | 1 + ui/tests/acceptance/chroot-namespace-test.js | 29 ++++++++++++++++++++ 5 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 ui/mirage/handlers/chroot-namespace.js create mode 100644 ui/tests/acceptance/chroot-namespace-test.js diff --git a/ui/app/adapters/cluster.js b/ui/app/adapters/cluster.js index c329f313a7be..7469e062cdff 100644 --- a/ui/app/adapters/cluster.js +++ b/ui/app/adapters/cluster.js @@ -80,17 +80,10 @@ export default ApplicationAdapter.extend({ performancestandbycode: 200, }, unauthenticated: true, - }).catch((e) => { - if (e.errors?.includes('invalid namespace specified for chroot_namespace')) { - // This error will occur if the call is made with chroot_namespace set on config - // but the specified namespace does not exist. In this case we should show the error. - e.message = - 'chroot_namespace has been set but the namespace does not exist. Please create the namespace and then refresh the page.'; - throw e; - } - // otherwise, sys/health will only fail when chroot set + }).catch(() => { + // sys/health will only fail when chroot set // because it's allowed in root namespace only and - // configured to return a 200 response in other cases + // configured to return a 200 response in other fail scenarios return { has_chroot_namespace: true }; }); }, diff --git a/ui/mirage/handlers/chroot-namespace.js b/ui/mirage/handlers/chroot-namespace.js new file mode 100644 index 000000000000..97a17e10074a --- /dev/null +++ b/ui/mirage/handlers/chroot-namespace.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { Response } from 'miragejs'; + +/* + These are mocked responses to mimic what we get from the server + when within a chrooted listener (assuming the namespace exists) + */ +export default function (server) { + server.get('sys/health', () => new Response(400, {}, { errors: ['unsupported path'] })); + server.get('sys/replication/status', () => new Response(400, {}, { errors: ['unsupported path'] })); +} diff --git a/ui/mirage/handlers/index.js b/ui/mirage/handlers/index.js index 7ddae8563173..574b2e0405cc 100644 --- a/ui/mirage/handlers/index.js +++ b/ui/mirage/handlers/index.js @@ -6,6 +6,7 @@ // add all handlers here // individual lookup done in mirage config import base from './base'; +import chrootNamespace from './chroot-namespace'; import clients from './clients'; import db from './db'; import hcpLink from './hcp-link'; @@ -19,6 +20,7 @@ import reducedDisclosure from './reduced-disclosure'; export { base, + chrootNamespace, clients, db, hcpLink, diff --git a/ui/package.json b/ui/package.json index e872b1147edf..7e199ccd3df3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -26,6 +26,7 @@ "fmt:styles": "prettier --write app/styles/**/*.*", "start": "VAULT_ADDR=http://localhost:8200; ember server --proxy=$VAULT_ADDR", "start2": "ember server --proxy=http://localhost:8202 --port=4202", + "start:chroot": "ember server --proxy=http://localhost:8300 --port=4300", "start:mirage": "start () { MIRAGE_DEV_HANDLER=$1 yarn run start; }; start", "test": "npm-run-all --print-name lint:js:quiet lint:hbs:quiet && node scripts/start-vault.js", "test:enos": "npm-run-all lint:js:quiet lint:hbs:quiet && node scripts/enos-test-ember.js", diff --git a/ui/tests/acceptance/chroot-namespace-test.js b/ui/tests/acceptance/chroot-namespace-test.js new file mode 100644 index 000000000000..5bca573f31c1 --- /dev/null +++ b/ui/tests/acceptance/chroot-namespace-test.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupApplicationTest } from 'ember-qunit'; +import { currentRouteName } from '@ember/test-helpers'; +import authPage from 'vault/tests/pages/auth'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import ENV from 'vault/config/environment'; + +module('Acceptance | chroot-namespace enterprise ui', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + hooks.before(function () { + ENV['ember-cli-mirage'].handler = 'chrootNamespace'; + }); + hooks.after(function () { + ENV['ember-cli-mirage'].handler = null; + }); + + test('it should render normally when chroot namespace exists', async function (assert) { + await authPage.login(); + assert.strictEqual(currentRouteName(), 'vault.cluster.dashboard', 'goes to dashboard page'); + assert.dom('[data-test-badge-namespace]').includesText('root', 'Shows root namespace badge'); + }); +}); From c5ff114ccf7b25496f08e6b68f00b749b9ec0079 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 2 Nov 2023 09:48:37 -0500 Subject: [PATCH 7/8] add changelog --- changelog/23942.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/23942.txt diff --git a/changelog/23942.txt b/changelog/23942.txt new file mode 100644 index 000000000000..a4d43d48f091 --- /dev/null +++ b/changelog/23942.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: fix broken GUI when accessing from listener with chroot_namespace defined +``` From fbf80162d320cfa5d3b05dfc8fa3bf98b55dadd3 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 2 Nov 2023 10:29:16 -0500 Subject: [PATCH 8/8] fix test for oss --- ui/tests/acceptance/dashboard-test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js index e34cfb8fccb3..cdf95e18208f 100644 --- a/ui/tests/acceptance/dashboard-test.js +++ b/ui/tests/acceptance/dashboard-test.js @@ -49,10 +49,11 @@ module('Acceptance | landing page dashboard', function (hooks) { await visit('/vault/dashboard'); const version = this.owner.lookup('service:version'); const versionName = version.version; - const versionNameEnd = version.isEnterprise ? versionName.indexOf('+') : versionName.length; - assert - .dom(SELECTORS.cardHeader('Vault version')) - .hasText(`Vault v${versionName.slice(0, versionNameEnd)} root`); + const versionText = version.isEnterprise + ? `Vault v${versionName.slice(0, versionName.indexOf('+'))} root` + : `Vault v${versionName}`; + + assert.dom(SELECTORS.cardHeader('Vault version')).hasText(versionText); }); module('secrets engines card', function (hooks) {