UI: VAULT-6519 PKI configuration read (#19677)

This commit is contained in:
Kianna 2023-03-22 14:14:11 -07:00 committed by GitHub
parent cde1b3e328
commit 84957ad993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 292 additions and 35 deletions

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { encodePath } from 'vault/utils/path-encoding-helpers';
import ApplicationAdapter from '../application';
export default class PkiCrlAdapter extends ApplicationAdapter {
namespace = 'v1';
_url(backend) {
return `${this.buildURL()}/${encodePath(backend)}/config/crl`;
}
findRecord(store, type, backend) {
return this.ajax(this._url(backend), 'GET').then((resp) => {
return resp.data;
});
}
}

View File

@ -62,4 +62,6 @@ export default class MountConfigModel extends Model {
noDefault: true,
})
tokenType;
@attr() allowedManagedKeys;
}

18
ui/app/models/pki/crl.js Normal file
View File

@ -0,0 +1,18 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Model, { attr } from '@ember-data/model';
export default class PkiCrlModel extends Model {
// This model uses the backend value as the model ID
get useOpenAPI() {
return true;
}
@attr('string') expiry;
@attr('boolean') autoRebuild;
@attr('string') ocspExpiry;
@attr('boolean') ocspDisable;
}

View File

@ -158,6 +158,9 @@
.has-padding-m {
padding: $spacing-m;
}
.has-bottom-padding-s {
padding-bottom: $spacing-s;
}
.has-padding-xs {
padding: $spacing-xs;
}

View File

@ -0,0 +1,33 @@
<h2 class="title has-bottom-margin-xs has-top-margin-m is-4 has-border-bottom-light has-bottom-padding-s">
Global URLs
</h2>
<InfoTableRow @label="Issuing certificates" @value={{or @urls.issuingCertificates "None"}} />
<InfoTableRow
@label="CRL distribution points"
@value={{if @urls.crlDistributionPoints @urls.crlDistributionPoints "None"}}
/>
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
Certificate Revocation List (CRL)
</h2>
<InfoTableRow @label="Expiry" @value={{@crl.expiry}} />
<InfoTableRow @label="Auto-rebuild" @value={{if @crl.autoRebuild "On" "Off"}} />
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
Online Certificate Status Protocol (OCSP)
</h2>
<InfoTableRow @label="Responder APIs" @value={{if @crl.ocspDisable "Disabled" "Enabled"}} />
<InfoTableRow @label="Interval" @value={{@crl.ocspExpiry}} />
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
Mount Configuration
</h2>
<InfoTableRow @label="Secret engine type" @value={{@mountConfig.engineType}} />
<InfoTableRow @label="Path" @value={{@mountConfig.path}} />
<InfoTableRow @label="Accessor" @value={{@mountConfig.accessor}} />
<InfoTableRow @label="Local" @value={{@mountConfig.local}} />
<InfoTableRow @label="Seal wrap" @value={{@mountConfig.sealWrap}} />
<InfoTableRow @label="Default lease TTL" @value={{@mountConfig.config.defaultLeaseTtl}} />
<InfoTableRow @label="Max lease TTL" @value={{@mountConfig.config.maxLeaseTtl}} />
<InfoTableRow @label="Allowed managed keys" @value={{or @mountConfig.config.allowedManagedKeys "None"}} />
<div class="has-top-margin-l"></div>

View File

@ -4,5 +4,31 @@
*/
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { withConfig } from 'pki/decorators/check-config';
import { hash } from 'rsvp';
export default class ConfigurationIndexRoute extends Route {}
@withConfig()
export default class ConfigurationIndexRoute extends Route {
@service store;
@service secretMountPath;
model() {
const backend = this.secretMountPath.currentPath;
return hash({
hasConfig: this.shouldPromptConfig,
engine: this.modelFor('application'),
urls: this.store.findRecord('pki/urls', backend),
crl: this.store.findRecord('pki/crl', backend),
mountConfig: this.fetchMountConfig(backend),
});
}
async fetchMountConfig(path) {
const mountConfig = await this.store.query('secret-engine', { path });
if (mountConfig) {
return mountConfig.get('firstObject');
}
}
}

View File

@ -1,32 +1,33 @@
<SecretListHeader
@model={{this.model}}
@model={{this.model.engine}}
@backendCrumb={{hash
label=this.model.id
text=this.model.id
label=this.model.engine.id
text=this.model.engine.id
path="vault.cluster.secrets.backend.list-root"
model=this.model.id
model=this.model.engine.id
}}
@isEngine={{true}}
/>
{{! <Toolbar>
<ToolbarActions>
<ToolbarLink @route="configuration.tidy">
Tidy
</ToolbarLink>
<ToolbarLink @route="configuration.edit">
Edit configuration
</ToolbarLink>
</ToolbarActions>
</Toolbar> }}
<AlertBanner
@type="info"
@title="PKI beta in use"
@message="To view the secret engine configuration or tune the mount, you'll have to return to the regular version."
class="has-top-margin-m"
data-test-pki-configuration-banner
>
<LinkToExternal @route="secretsListRootConfiguration" class="info">
Return to old PKI to configure
</LinkToExternal>
</AlertBanner>
{{#if this.model.hasConfig}}
<Toolbar>
<ToolbarActions>
<ToolbarLink @route="configuration.tidy">
Tidy
</ToolbarLink>
<ToolbarLink @route="configuration.edit">
Edit configuration
</ToolbarLink>
</ToolbarActions>
</Toolbar>
<Page::PkiConfigurationDetails @urls={{this.model.urls}} @crl={{this.model.crl}} @mountConfig={{this.model.mountConfig}} />
{{else}}
<Toolbar />
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>
</EmptyState>
{{/if}}

View File

@ -75,15 +75,6 @@ module('Acceptance | pki workflow', function (hooks) {
await click(SELECTORS.keysTab);
assertEmptyState(assert, 'keys');
});
test('shows pki beta banner to return to old pki on new pki configuration page', async function (assert) {
assert.expect(3);
await authPage.login(this.pkiAdminToken);
await visit(`/vault/secrets/${this.mountPath}/pki/configuration`);
assert.dom(SELECTORS.configTab).exists('Configuration tab is present');
assert.dom(SELECTORS.configuration.pkiBetaBanner).exists('Configuration beta banner exists');
await click(SELECTORS.configuration.pkiBetaBannerLink);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/configuration`);
});
module('configuration', function (hooks) {
hooks.beforeEach(function () {

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
export const SELECTORS = {
// global urls
issuingCertificatesLabel: '[data-test-row-label="Issuing certificates"]',
issuingCertificatesRowVal: '[data-test-row-value="Issuing certificates"]',
crlDistributionPointsLabel: '[data-test-row-label="CRL distribution points"]',
crlDistributionPointsRowVal: '[data-test-row-value="CRL distribution points"]',
// crl
expiryLabel: '[data-test-row-label="Expiry"]',
expiryRowVal: '[data-test-row-value="Expiry"]',
rebuildLabel: '[data-test-row-label="Auto-rebuild"]',
rebuildRowVal: '[data-test-row-value="Auto-rebuild"]',
responderApiLabel: '[data-test-row-label="Responder APIs"]',
responderApiRowVal: '[data-test-row-value="Responder APIs"]',
intervalLabel: '[data-test-row-label="Interval"]',
intervalRowVal: '[data-test-row-value="Interval"]',
// mount configuration
engineTypeLabel: '[data-test-row-label="Secret engine type"]',
engineTypeRowVal: '[data-test-row-value="Secret engine type"]',
pathLabel: '[data-test-row-label="Path"]',
pathRowVal: '[data-test-row-value="Path"]',
accessorLabel: '[data-test-row-label="Accessor"]',
accessorRowVal: '[data-test-row-value="Accessor"]',
localLabel: '[data-test-row-label="Local"]',
localRowVal: '[data-test-value-div="Local"]',
sealWrapLabel: '[data-test-row-label="Seal wrap"]',
sealWrapRowVal: '[data-test-value-div="Seal wrap"]',
maxLeaseTtlLabel: '[data-test-row-label="Max lease TTL"]',
maxLeaseTtlRowVal: '[data-test-row-value="Max lease TTL"]',
allowedManagedKeysLabel: '[data-test-row-label="Allowed managed keys"]',
allowedManagedKeysRowVal: '[data-test-value-div="Allowed managed keys"]',
};

View File

@ -0,0 +1,126 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupEngine } from 'ember-engines/test-support';
import { SELECTORS } from 'vault/tests/helpers/pki/page/pki-configuration-details';
module('Integration | Component | Page::PkiConfigurationDetails', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'pki');
hooks.beforeEach(function () {
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
this.secretMountPath.currentPath = 'pki-test';
this.store = this.owner.lookup('service:store');
this.urls = this.store.createRecord('pki/urls', { id: 'pki-test', issuingCertificates: 'example.com' });
this.crl = this.store.createRecord('pki/crl', {
id: 'pki-test',
expiry: '20h',
autoRebuild: false,
ocspExpiry: '77h',
oscpDisable: true,
});
this.mountConfig = {
id: 'pki-test',
engineType: 'pki',
path: '/pki-test',
accessor: 'pki_33345b0d',
local: false,
sealWrap: true,
config: this.store.createRecord('mount-config', {
defaultLease: '12h',
maxLeaseTtl: '400h',
allowedManagedKeys: true,
}),
};
});
test('shows the correct information on global urls section', async function (assert) {
await render(
hbs`<Page::PkiConfigurationDetails @urls={{this.urls}} @crl={{this.crl}} @mountConfig={{this.mountConfig}} />,`,
{ owner: this.engine }
);
assert
.dom(SELECTORS.issuingCertificatesLabel)
.hasText('Issuing certificates', 'issuing certificate row label renders');
assert
.dom(SELECTORS.issuingCertificatesRowVal)
.hasText('example.com', 'issuing certificate value renders');
this.urls.issuingCertificates = null;
await render(
hbs`<Page::PkiConfigurationDetails @urls={{this.urls}} @crl={{this.crl}} @mountConfig={{this.mountConfig}} />,`,
{ owner: this.engine }
);
assert
.dom(SELECTORS.issuingCertificatesRowVal)
.hasText('None', 'issuing certificate value renders None if none is configured');
assert
.dom(SELECTORS.crlDistributionPointsLabel)
.hasText('CRL distribution points', 'crl distribution points row label renders');
assert
.dom(SELECTORS.crlDistributionPointsRowVal)
.hasText('None', 'crl distribution points value renders None if none is configured');
});
test('shows the correct information on crl section', async function (assert) {
await render(
hbs`<Page::PkiConfigurationDetails @urls={{this.urls}} @crl={{this.crl}} @mountConfig={{this.mountConfig}} />,`,
{ owner: this.engine }
);
assert.dom(SELECTORS.expiryLabel).hasText('Expiry', 'crl expiry row label renders');
assert.dom(SELECTORS.expiryRowVal).hasText('20h', 'expiry value renders');
assert.dom(SELECTORS.rebuildLabel).hasText('Auto-rebuild', 'auto rebuild label renders');
assert
.dom(SELECTORS.rebuildRowVal)
.hasText('Off', 'auto-rebuild value renders off if auto rebuild is false');
this.crl.autoRebuild = true;
await render(
hbs`<Page::PkiConfigurationDetails @urls={{this.urls}} @crl={{this.crl}} @mountConfig={{this.mountConfig}} />,`,
{ owner: this.engine }
);
assert
.dom(SELECTORS.rebuildRowVal)
.hasText('On', 'auto-rebuild value renders on if auto rebuild is true');
assert.dom(SELECTORS.responderApiLabel).hasText('Responder APIs', 'responder apis row label renders');
assert
.dom(SELECTORS.responderApiRowVal)
.hasText('Enabled', 'responder apis value renders Enabled if oscpDisable is true');
assert.dom(SELECTORS.intervalLabel).hasText('Interval', 'interval row label renders');
assert.dom(SELECTORS.intervalRowVal).hasText('77h', 'interval value renders');
});
test('shows the correct information on mount configuration section', async function (assert) {
await render(
hbs`<Page::PkiConfigurationDetails @urls={{this.urls}} @crl={{this.crl}} @mountConfig={{this.mountConfig}} />,`,
{ owner: this.engine }
);
assert.dom(SELECTORS.engineTypeLabel).hasText('Secret engine type', 'engine type row label renders');
assert.dom(SELECTORS.engineTypeRowVal).hasText('pki', 'engine type row value renders');
assert.dom(SELECTORS.pathLabel).hasText('Path', 'path row label renders');
assert.dom(SELECTORS.pathRowVal).hasText('/pki-test', 'path row value renders');
assert.dom(SELECTORS.accessorLabel).hasText('Accessor', 'accessor row label renders');
assert.dom(SELECTORS.accessorRowVal).hasText('pki_33345b0d', 'accessor row value renders');
assert.dom(SELECTORS.localLabel).hasText('Local', 'local row label renders');
assert.dom(SELECTORS.localRowVal).hasText('No', 'local row value renders');
assert.dom(SELECTORS.sealWrapLabel).hasText('Seal wrap', 'seal wrap row label renders');
assert
.dom(SELECTORS.sealWrapRowVal)
.hasText('Yes', 'seal wrap row value renders Yes if sealWrap is true');
assert.dom(SELECTORS.maxLeaseTtlLabel).hasText('Max lease TTL', 'max lease label renders');
assert.dom(SELECTORS.maxLeaseTtlRowVal).hasText('400h', 'max lease value renders');
assert
.dom(SELECTORS.allowedManagedKeysLabel)
.hasText('Allowed managed keys', 'allowed managed keys label renders');
assert.dom(SELECTORS.allowedManagedKeysRowVal).hasText('Yes', 'allowed managed keys value renders');
});
});