mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
UI: chroot namespace listener (#23942)
This commit is contained in:
3
changelog/23942.txt
Normal file
3
changelog/23942.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:bug
|
||||||
|
ui: fix broken GUI when accessing from listener with chroot_namespace defined
|
||||||
|
```
|
||||||
@@ -80,6 +80,11 @@ export default ApplicationAdapter.extend({
|
|||||||
performancestandbycode: 200,
|
performancestandbycode: 200,
|
||||||
},
|
},
|
||||||
unauthenticated: true,
|
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 };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export default class ClusterModel extends Model {
|
|||||||
@attr('boolean') standby;
|
@attr('boolean') standby;
|
||||||
@attr('string') type;
|
@attr('string') type;
|
||||||
@attr('object') license;
|
@attr('object') license;
|
||||||
|
// manually set on response when sys/health failure
|
||||||
|
@attr('boolean') hasChrootNamespace;
|
||||||
|
|
||||||
/* Licensing concerns */
|
/* Licensing concerns */
|
||||||
get licenseExpiry() {
|
get licenseExpiry() {
|
||||||
|
|||||||
@@ -9,17 +9,16 @@ import { alias, and, equal } from '@ember/object/computed';
|
|||||||
export default Model.extend({
|
export default Model.extend({
|
||||||
name: attr('string'),
|
name: attr('string'),
|
||||||
// https://developer.hashicorp.com/vault/api-docs/system/health
|
// https://developer.hashicorp.com/vault/api-docs/system/health
|
||||||
initialized: attr('boolean'),
|
|
||||||
sealed: attr('boolean'),
|
|
||||||
isSealed: alias('sealed'),
|
|
||||||
standby: attr('boolean'),
|
standby: attr('boolean'),
|
||||||
isActive: equal('standby', false),
|
isActive: equal('standby', false),
|
||||||
clusterName: attr('string'),
|
|
||||||
clusterId: attr('string'),
|
clusterId: attr('string'),
|
||||||
|
|
||||||
isLeader: and('initialized', 'isActive'),
|
isLeader: and('initialized', 'isActive'),
|
||||||
|
|
||||||
// https://developer.hashicorp.com/vault/api-docs/system/seal-status
|
// 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.
|
// The "t" parameter is the threshold, and "n" is the number of shares.
|
||||||
t: attr('number'),
|
t: attr('number'),
|
||||||
n: attr('number'),
|
n: attr('number'),
|
||||||
|
|||||||
@@ -29,18 +29,20 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
|
|||||||
|
|
||||||
model() {
|
model() {
|
||||||
const clusterModel = this.modelFor('vault.cluster');
|
const clusterModel = this.modelFor('vault.cluster');
|
||||||
const replication = {
|
const hasChroot = clusterModel?.hasChrootNamespace;
|
||||||
dr: clusterModel.dr,
|
const replication = hasChroot
|
||||||
performance: clusterModel.performance,
|
? null
|
||||||
};
|
: {
|
||||||
|
dr: clusterModel.dr,
|
||||||
|
performance: clusterModel.performance,
|
||||||
|
};
|
||||||
return hash({
|
return hash({
|
||||||
replication,
|
replication,
|
||||||
secretsEngines: this.store.query('secret-engine', {}),
|
secretsEngines: this.store.query('secret-engine', {}),
|
||||||
license: this.store.queryRecord('license', {}).catch(() => null),
|
license: this.store.queryRecord('license', {}).catch(() => null),
|
||||||
isRootNamespace: this.namespace.inRootNamespace,
|
isRootNamespace: this.namespace.inRootNamespace && !hasChroot,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
vaultConfiguration: this.getVaultConfiguration(),
|
vaultConfiguration: hasChroot ? null : this.getVaultConfiguration(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default class VersionService extends Service {
|
|||||||
@task
|
@task
|
||||||
*getVersion() {
|
*getVersion() {
|
||||||
if (this.version) return;
|
if (this.version) return;
|
||||||
const response = yield this.store.adapterFor('cluster').health();
|
const response = yield this.store.adapterFor('cluster').sealStatus();
|
||||||
this.version = response.version;
|
this.version = response.version;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
46
ui/app/templates/error.hbs
Normal file
46
ui/app/templates/error.hbs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{{!
|
||||||
|
Copyright (c) HashiCorp, Inc.
|
||||||
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
|
~}}
|
||||||
|
|
||||||
|
<div class="is-flex-grow-1 is-flex-v-centered">
|
||||||
|
<div class="empty-state-content">
|
||||||
|
<div class="is-flex-v-centered has-bottom-margin-xxl">
|
||||||
|
<div class="brand-icon-large">
|
||||||
|
<Icon @name="vault" @size="24" @stretched={{true}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="is-flex-center">
|
||||||
|
<div class="error-icon">
|
||||||
|
<Icon @name="help" @size="24" class="has-text-grey" @stretched={{true}} />
|
||||||
|
</div>
|
||||||
|
<div class="has-left-margin-s">
|
||||||
|
<h1 class="is-size-4 has-text-semibold has-text-grey has-line-height-1">
|
||||||
|
{{#if (eq this.model.httpStatus 403)}}
|
||||||
|
Not authorized
|
||||||
|
{{else if (eq this.model.httpStatus 404)}}
|
||||||
|
Page not found
|
||||||
|
{{else}}
|
||||||
|
Error
|
||||||
|
{{/if}}
|
||||||
|
</h1>
|
||||||
|
<p class="has-text-grey is-size-8">Error {{this.model.httpStatus}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="has-text-grey has-top-margin-m has-bottom-padding-l has-border-bottom-light" data-test-error-description>
|
||||||
|
{{this.model.message}}
|
||||||
|
{{join ". " this.model.errors}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="is-flex-between has-top-margin-s">
|
||||||
|
<ExternalLink @href="/" @sameTab={{true}} class="is-no-underline is-flex-center has-text-semibold">
|
||||||
|
<Chevron @direction="left" />
|
||||||
|
Go home
|
||||||
|
</ExternalLink>
|
||||||
|
<DocLink @path="/vault/api-docs#http-status-codes">
|
||||||
|
Learn more
|
||||||
|
</DocLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -4,12 +4,12 @@
|
|||||||
~}}
|
~}}
|
||||||
|
|
||||||
<Dashboard::Overview
|
<Dashboard::Overview
|
||||||
@replication={{@model.replication}}
|
@replication={{this.model.replication}}
|
||||||
@secretsEngines={{@model.secretsEngines}}
|
@secretsEngines={{this.model.secretsEngines}}
|
||||||
@license={{@model.license}}
|
@license={{this.model.license}}
|
||||||
@isRootNamespace={{@model.isRootNamespace}}
|
@isRootNamespace={{this.model.isRootNamespace}}
|
||||||
@version={{@model.version}}
|
@version={{this.model.version}}
|
||||||
@vaultConfiguration={{@model.vaultConfiguration}}
|
@vaultConfiguration={{this.model.vaultConfiguration}}
|
||||||
@refreshModel={{this.refreshModel}}
|
@refreshModel={{this.refreshModel}}
|
||||||
@replicationUpdatedAt={{this.replicationUpdatedAt}}
|
@replicationUpdatedAt={{this.replicationUpdatedAt}}
|
||||||
/>
|
/>
|
||||||
15
ui/mirage/handlers/chroot-namespace.js
Normal file
15
ui/mirage/handlers/chroot-namespace.js
Normal file
@@ -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'] }));
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
// add all handlers here
|
// add all handlers here
|
||||||
// individual lookup done in mirage config
|
// individual lookup done in mirage config
|
||||||
import base from './base';
|
import base from './base';
|
||||||
|
import chrootNamespace from './chroot-namespace';
|
||||||
import clients from './clients';
|
import clients from './clients';
|
||||||
import db from './db';
|
import db from './db';
|
||||||
import hcpLink from './hcp-link';
|
import hcpLink from './hcp-link';
|
||||||
@@ -19,6 +20,7 @@ import reducedDisclosure from './reduced-disclosure';
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
base,
|
base,
|
||||||
|
chrootNamespace,
|
||||||
clients,
|
clients,
|
||||||
db,
|
db,
|
||||||
hcpLink,
|
hcpLink,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"fmt:styles": "prettier --write app/styles/**/*.*",
|
"fmt:styles": "prettier --write app/styles/**/*.*",
|
||||||
"start": "VAULT_ADDR=http://localhost:8200; ember server --proxy=$VAULT_ADDR",
|
"start": "VAULT_ADDR=http://localhost:8200; ember server --proxy=$VAULT_ADDR",
|
||||||
"start2": "ember server --proxy=http://localhost:8202 --port=4202",
|
"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",
|
"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": "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",
|
"test:enos": "npm-run-all lint:js:quiet lint:hbs:quiet && node scripts/enos-test-ember.js",
|
||||||
|
|||||||
29
ui/tests/acceptance/chroot-namespace-test.js
Normal file
29
ui/tests/acceptance/chroot-namespace-test.js
Normal file
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -49,10 +49,11 @@ module('Acceptance | landing page dashboard', function (hooks) {
|
|||||||
await visit('/vault/dashboard');
|
await visit('/vault/dashboard');
|
||||||
const version = this.owner.lookup('service:version');
|
const version = this.owner.lookup('service:version');
|
||||||
const versionName = version.version;
|
const versionName = version.version;
|
||||||
const versionNameEnd = version.isEnterprise ? versionName.indexOf('+') : versionName.length;
|
const versionText = version.isEnterprise
|
||||||
assert
|
? `Vault v${versionName.slice(0, versionName.indexOf('+'))} root`
|
||||||
.dom(SELECTORS.cardHeader('Vault version'))
|
: `Vault v${versionName}`;
|
||||||
.hasText(`Vault v${versionName.slice(0, versionNameEnd)} root`);
|
|
||||||
|
assert.dom(SELECTORS.cardHeader('Vault version')).hasText(versionText);
|
||||||
});
|
});
|
||||||
|
|
||||||
module('secrets engines card', function (hooks) {
|
module('secrets engines card', function (hooks) {
|
||||||
|
|||||||
Reference in New Issue
Block a user