UI: chroot namespace listener (#23942)

This commit is contained in:
Chelsea Shaw
2023-11-02 12:55:09 -05:00
committed by GitHub
parent 750ab337ea
commit 3eb205a87d
13 changed files with 127 additions and 22 deletions

3
changelog/23942.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
ui: fix broken GUI when accessing from listener with chroot_namespace defined
```

View File

@@ -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 };
});
},

View File

@@ -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() {

View File

@@ -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'),

View File

@@ -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(),
});
}

View File

@@ -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;
}

View 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>

View File

@@ -4,12 +4,12 @@
~}}
<Dashboard::Overview
@replication={{@model.replication}}
@secretsEngines={{@model.secretsEngines}}
@license={{@model.license}}
@isRootNamespace={{@model.isRootNamespace}}
@version={{@model.version}}
@vaultConfiguration={{@model.vaultConfiguration}}
@replication={{this.model.replication}}
@secretsEngines={{this.model.secretsEngines}}
@license={{this.model.license}}
@isRootNamespace={{this.model.isRootNamespace}}
@version={{this.model.version}}
@vaultConfiguration={{this.model.vaultConfiguration}}
@refreshModel={{this.refreshModel}}
@replicationUpdatedAt={{this.replicationUpdatedAt}}
/>

View 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'] }));
}

View File

@@ -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,

View File

@@ -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",

View 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');
});
});

View File

@@ -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) {