Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: chroot namespace listener #23942

Merged
merged 9 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/23942.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: fix broken GUI when accessing from listener with chroot_namespace defined
```
5 changes: 5 additions & 0 deletions ui/app/adapters/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
});
},

Expand Down
2 changes: 2 additions & 0 deletions ui/app/models/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
7 changes: 3 additions & 4 deletions ui/app/models/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clusterName isn't used anywhere, so removed it from the model. The other values are also available on seal-status which will be available in a chroot scenario, so I moved those below to that section

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'),
Expand Down
16 changes: 9 additions & 7 deletions ui/app/routes/vault/cluster/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
});
}

Expand Down
2 changes: 1 addition & 1 deletion ui/app/services/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting version from this endpoint instead since it's available in non-root namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will probably be updated again to a UI-specific endpoint in the near future, but this will do for now.

this.version = response.version;
return;
}
Expand Down
46 changes: 46 additions & 0 deletions ui/app/templates/error.hbs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the error was happening in the cluster route, we needed to add this highest-level error template to show the error.

Original file line number Diff line number Diff line change
@@ -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>
12 changes: 6 additions & 6 deletions ui/app/templates/vault/cluster/dashboard.hbs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are subtle bugs introduced when you use {{@model}} instead of {{this.model}} in route templates -- https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-model-argument-in-route-templates.md
This route isn't problematic since it doesn't use route params, but in terms of best practices we should make route templates consistently use this.model
Shoutout to @fivetanley for sharing this knowledge (shamelessly stole your language)

Original file line number Diff line number Diff line change
Expand Up @@ -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}}
/>
15 changes: 15 additions & 0 deletions ui/mirage/handlers/chroot-namespace.js
Original file line number Diff line number Diff line change
@@ -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'] }));
}
2 changes: 2 additions & 0 deletions ui/mirage/handlers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -19,6 +20,7 @@ import reducedDisclosure from './reduced-disclosure';

export {
base,
chrootNamespace,
clients,
db,
hcpLink,
Expand Down
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 29 additions & 0 deletions ui/tests/acceptance/chroot-namespace-test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
9 changes: 5 additions & 4 deletions ui/tests/acceptance/dashboard-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this test because the root namespace badge shouldn't show in community version

});

module('secrets engines card', function (hooks) {
Expand Down
Loading