From 3eb205a87d80bfd78606ac76cacf232dcaa5ebc4 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:55:09 -0500 Subject: [PATCH] UI: chroot namespace listener (#23942) --- changelog/23942.txt | 3 ++ ui/app/adapters/cluster.js | 5 +++ ui/app/models/cluster.js | 2 + ui/app/models/node.js | 7 ++- ui/app/routes/vault/cluster/dashboard.js | 16 ++++--- ui/app/services/version.js | 2 +- ui/app/templates/error.hbs | 46 ++++++++++++++++++++ ui/app/templates/vault/cluster/dashboard.hbs | 12 ++--- 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 ++++++++++++ ui/tests/acceptance/dashboard-test.js | 9 ++-- 13 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 changelog/23942.txt create mode 100644 ui/app/templates/error.hbs create mode 100644 ui/mirage/handlers/chroot-namespace.js create mode 100644 ui/tests/acceptance/chroot-namespace-test.js diff --git a/changelog/23942.txt b/changelog/23942.txt new file mode 100644 index 0000000000..a4d43d48f0 --- /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 +``` diff --git a/ui/app/adapters/cluster.js b/ui/app/adapters/cluster.js index de173feb88..7469e062cd 100644 --- a/ui/app/adapters/cluster.js +++ b/ui/app/adapters/cluster.js @@ -80,6 +80,11 @@ export default ApplicationAdapter.extend({ performancestandbycode: 200, }, unauthenticated: true, + }).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 fail scenarios + return { has_chroot_namespace: true }; }); }, diff --git a/ui/app/models/cluster.js b/ui/app/models/cluster.js index 543ed7bf0a..6ac7f90af3 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() { diff --git a/ui/app/models/node.js b/ui/app/models/node.js index 520368c72d..003d3f7ab9 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/routes/vault/cluster/dashboard.js b/ui/app/routes/vault/cluster/dashboard.js index b50a68c1db..37b2cb88b7 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/services/version.js b/ui/app/services/version.js index fe1ec0aae8..cf99b759c7 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; } diff --git a/ui/app/templates/error.hbs b/ui/app/templates/error.hbs new file mode 100644 index 0000000000..9448637df4 --- /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 diff --git a/ui/app/templates/vault/cluster/dashboard.hbs b/ui/app/templates/vault/cluster/dashboard.hbs index fad7cd54d7..b7f212674a 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 diff --git a/ui/mirage/handlers/chroot-namespace.js b/ui/mirage/handlers/chroot-namespace.js new file mode 100644 index 0000000000..97a17e1007 --- /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 7ddae85631..574b2e0405 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 e872b1147e..7e199ccd3d 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 0000000000..5bca573f31 --- /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'); + }); +}); diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js index e34cfb8fcc..cdf95e1820 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) {