UI: refactor to use pki/action model for importing a pem bundle (#19425)

* rename component test file

* rename component

* rename file again..

* rename component file and remove import from issuer adapter

* rename hbs file

* update to new component name, use pki/action

* update test selectors

* update tests

* update workflow test

* add useIssuer to adapter options
This commit is contained in:
claire bontempo 2023-03-02 15:38:39 -08:00 committed by GitHub
parent 87c9649515
commit a22bb9bfcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 103 additions and 50 deletions

View File

@ -26,16 +26,6 @@ export default class PkiIssuerAdapter extends ApplicationAdapter {
} }
} }
createRecord(store, type, snapshot) {
let url = this.urlForQuery(this._getBackend(snapshot));
if (snapshot.adapterOptions.import) {
url = `${url}/import/bundle`;
}
return this.ajax(url, 'POST', { data: this.serialize(snapshot) }).then((resp) => {
return resp;
});
}
updateRecord(store, type, snapshot) { updateRecord(store, type, snapshot) {
const { issuerId } = snapshot.record; const { issuerId } = snapshot.record;
const backend = this._getBackend(snapshot); const backend = this._getBackend(snapshot);

View File

@ -28,6 +28,10 @@ export default class PkiActionModel extends Model {
/* actionType import */ /* actionType import */
@attr('string') pemBundle; @attr('string') pemBundle;
// readonly attrs returned after importing
@attr importedIssuers;
@attr importedKeys;
@attr mapping;
/* actionType generate-root */ /* actionType generate-root */
@attr('string', { @attr('string', {

View File

@ -30,7 +30,7 @@
</div> </div>
{{/unless}} {{/unless}}
{{#if (eq @config.actionType "import")}} {{#if (eq @config.actionType "import")}}
<PkiCaCertificateImport <PkiImportPemBundle
@model={{@config}} @model={{@config}}
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.overview"}} @onCancel={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
@onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers"}} @onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers"}}

View File

@ -3,7 +3,7 @@
<label class="title has-padding-top is-5"> <label class="title has-padding-top is-5">
Certificate parameters Certificate parameters
</label> </label>
<form {{on "submit" (perform this.submitForm)}} data-test-pki-ca-cert-import-form> <form {{on "submit" (perform this.submitForm)}} data-test-pki-import-pem-bundle-form>
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" /> <MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
<div class="box is-sideless is-fullwidth is-marginless has-top-padding-l"> <div class="box is-sideless is-fullwidth is-marginless has-top-padding-l">
<TextFile @onChange={{this.onFileUploaded}} @label="PEM Bundle" /> <TextFile @onChange={{this.onFileUploaded}} @label="PEM Bundle" />
@ -17,7 +17,7 @@
type="submit" type="submit"
class="button is-primary {{if this.submitForm.isRunning 'is-loading'}}" class="button is-primary {{if this.submitForm.isRunning 'is-loading'}}"
disabled={{this.submitForm.isRunning}} disabled={{this.submitForm.isRunning}}
data-test-pki-ca-cert-import data-test-pki-import-pem-bundle
> >
Import issuer Import issuer
</button> </button>

View File

@ -6,17 +6,16 @@ import { task } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking'; import { tracked } from '@glimmer/tracking';
import { waitFor } from '@ember/test-waiters'; import { waitFor } from '@ember/test-waiters';
import errorMessage from 'vault/utils/error-message'; import errorMessage from 'vault/utils/error-message';
import PkiIssuerModel from 'vault/models/pki/issuer';
import PkiActionModel from 'vault/models/pki/action'; import PkiActionModel from 'vault/models/pki/action';
/** /**
* @module PkiCaCertificateImport * @module PkiImportPemBundle
* PkiCaCertificateImport components are used to import PKI CA certificates and keys via pem_bundle. * PkiImportPemBundle components are used to import PKI CA certificates and keys via pem_bundle.
* https://github.com/hashicorp/vault/blob/main/website/content/api-docs/secret/pki.mdx#import-ca-certificates-and-keys * https://github.com/hashicorp/vault/blob/main/website/content/api-docs/secret/pki.mdx#import-ca-certificates-and-keys
* *
* @example * @example
* ```js * ```js
* <PkiCaCertificateImport @model={{this.model}} /> * <PkiImportPemBundle @model={{this.model}} />
* ``` * ```
* *
* @param {Object} model - certificate model from route * @param {Object} model - certificate model from route
@ -24,14 +23,18 @@ import PkiActionModel from 'vault/models/pki/action';
* @callback onSubmit - Callback triggered on submit success. * @callback onSubmit - Callback triggered on submit success.
*/ */
interface AdapterOptions {
actionType: string;
useIssuer: boolean | undefined;
}
interface Args { interface Args {
onSave: CallableFunction; onSave: CallableFunction;
onCancel: CallableFunction; onCancel: CallableFunction;
model: PkiIssuerModel | PkiActionModel; model: PkiActionModel;
adapterOptions: object | undefined; adapterOptions: AdapterOptions;
} }
export default class PkiCaCertificateImport extends Component<Args> { export default class PkiImportPemBundle extends Component<Args> {
@service declare readonly flashMessages: FlashMessageService; @service declare readonly flashMessages: FlashMessageService;
@tracked errorBanner = ''; @tracked errorBanner = '';
@ -42,7 +45,7 @@ export default class PkiCaCertificateImport extends Component<Args> {
event.preventDefault(); event.preventDefault();
try { try {
yield this.args.model.save({ adapterOptions: this.args.adapterOptions }); yield this.args.model.save({ adapterOptions: this.args.adapterOptions });
this.flashMessages.success('Successfully imported certificate.'); this.flashMessages.success('Successfully imported data.');
this.args.onSave(); this.args.onSave();
} catch (error) { } catch (error) {
this.errorBanner = errorMessage(error); this.errorBanner = errorMessage(error);

View File

@ -195,8 +195,8 @@ export default class PkiIssuerCrossSign extends Component {
// the newly issued intermediate CA, so that they can do recovery // the newly issued intermediate CA, so that they can do recovery
// as they'd like. // as they'd like.
const issuerId = await this.store const issuerId = await this.store
.createRecord('pki/issuer', { pemBundle: signedCaChain }) .createRecord('pki/action', { pemBundle: signedCaChain })
.save({ adapterOptions: { import: true, mount: intMount } }) .save({ adapterOptions: { actionType: 'import', mount: intMount, useIssuer: true } })
.then((importedIssuer) => { .then((importedIssuer) => {
return Object.keys(importedIssuer.mapping).find( return Object.keys(importedIssuer.mapping).find(
// matching key is the issuer_id // matching key is the issuer_id

View File

@ -7,7 +7,7 @@ export default class PkiIssuersImportRoute extends PkiIssuersIndexRoute {
@service store; @service store;
model() { model() {
return this.store.createRecord('pki/issuer'); return this.store.createRecord('pki/action');
} }
setupController(controller, resolvedModel) { setupController(controller, resolvedModel) {

View File

@ -8,10 +8,9 @@
</h1> </h1>
</p.levelLeft> </p.levelLeft>
</PageHeader> </PageHeader>
<PkiImportPemBundle
<PkiCaCertificateImport
@model={{this.model}} @model={{this.model}}
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}} @onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
@onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}} @onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
@adapterOptions={{hash import=true}} @adapterOptions={{hash actionType="import" useIssuer=true}}
/> />

View File

@ -163,18 +163,18 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
await visit(`/vault/secrets/${this.mountPath}/pki/overview`); await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
await click(SELECTORS.issuersTab); await click(SELECTORS.issuersTab);
issuers = this.store.peekAll('pki/issuer'); issuers = this.store.peekAll('pki/issuer');
assert.strictEqual(issuers.length, 0, 'No issuers exist yet'); assert.strictEqual(issuers.length, 0, 'No issuer models exist yet');
await click(SELECTORS.importIssuerLink); await click(SELECTORS.importIssuerLink);
issuers = this.store.peekAll('pki/issuer'); issuers = this.store.peekAll('pki/action');
assert.strictEqual(issuers.length, 1, 'Issuer model created'); assert.strictEqual(issuers.length, 1, 'Action model created');
const issuer = issuers.objectAt(0); const issuer = issuers.objectAt(0);
assert.true(issuer.hasDirtyAttributes, 'Issuer has dirty attrs'); assert.true(issuer.hasDirtyAttributes, 'Action has dirty attrs');
assert.true(issuer.isNew, 'Issuer is new'); assert.true(issuer.isNew, 'Action is new');
// Exit // Exit
await click('[data-test-pki-ca-cert-cancel]'); await click('[data-test-pki-ca-cert-cancel]');
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/issuers`); assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/issuers`);
issuers = this.store.peekAll('pki/issuer'); issuers = this.store.peekAll('pki/action');
assert.strictEqual(issuers.length, 0, 'Issuer is removed from store'); assert.strictEqual(issuers.length, 0, 'Action is removed from store');
}); });
test('import issuer exit via breadcrumb', async function (assert) { test('import issuer exit via breadcrumb', async function (assert) {
let issuers; let issuers;
@ -184,15 +184,15 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
issuers = this.store.peekAll('pki/issuer'); issuers = this.store.peekAll('pki/issuer');
assert.strictEqual(issuers.length, 0, 'No issuers exist yet'); assert.strictEqual(issuers.length, 0, 'No issuers exist yet');
await click(SELECTORS.importIssuerLink); await click(SELECTORS.importIssuerLink);
issuers = this.store.peekAll('pki/issuer'); issuers = this.store.peekAll('pki/action');
assert.strictEqual(issuers.length, 1, 'Issuer model created'); assert.strictEqual(issuers.length, 1, 'Action model created');
const issuer = issuers.objectAt(0); const issuer = issuers.objectAt(0);
assert.true(issuer.hasDirtyAttributes, 'Issuer has dirty attrs'); assert.true(issuer.hasDirtyAttributes, 'Action model has dirty attrs');
assert.true(issuer.isNew, 'Issuer is new'); assert.true(issuer.isNew, 'Action model is new');
// Exit // Exit
await click(SELECTORS.overviewBreadcrumb); await click(SELECTORS.overviewBreadcrumb);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`); assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
issuers = this.store.peekAll('pki/issuer'); issuers = this.store.peekAll('pki/action');
assert.strictEqual(issuers.length, 0, 'Issuer is removed from store'); assert.strictEqual(issuers.length, 0, 'Issuer is removed from store');
}); });
test('generate root exit via cancel', async function (assert) { test('generate root exit via cancel', async function (assert) {

View File

@ -94,7 +94,7 @@ module('Acceptance | pki workflow', function (hooks) {
assert.dom(SELECTORS.configuration.emptyState).doesNotExist(); assert.dom(SELECTORS.configuration.emptyState).doesNotExist();
await click('[data-test-text-toggle]'); await click('[data-test-text-toggle]');
await fillIn('[data-test-text-file-textarea]', this.pemBundle); await fillIn('[data-test-text-file-textarea]', this.pemBundle);
await click('[data-test-pki-ca-cert-import]'); await click('[data-test-pki-import-pem-bundle]');
assert.strictEqual( assert.strictEqual(
currentURL(), currentURL(),
`/vault/secrets/${this.mountPath}/pki/issuers`, `/vault/secrets/${this.mountPath}/pki/issuers`,

View File

@ -9,7 +9,7 @@ export const SELECTORS = {
// pki-generate-root // pki-generate-root
...GENERATE_ROOT, ...GENERATE_ROOT,
// pki-ca-cert-import // pki-ca-cert-import
importForm: '[data-test-pki-ca-cert-import-form]', importForm: '[data-test-pki-import-pem-bundle-form]',
// generate-intermediate // generate-intermediate
csrDetails: '[data-test-generate-csr-result]', csrDetails: '[data-test-generate-csr-result]',
}; };

View File

@ -6,14 +6,14 @@ import { setupEngine } from 'ember-engines/test-support';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import { issuerPemBundle } from 'vault/tests/helpers/pki/values'; import { issuerPemBundle } from 'vault/tests/helpers/pki/values';
module('Integration | Component | pki issuer import', function (hooks) { module('Integration | Component | PkiImportPemBundle', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
setupMirage(hooks); setupMirage(hooks);
setupEngine(hooks, 'pki'); // https://github.com/ember-engines/ember-engines/pull/653 setupEngine(hooks, 'pki'); // https://github.com/ember-engines/ember-engines/pull/653
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
this.model = this.store.createRecord('pki/issuer'); this.model = this.store.createRecord('pki/action');
this.backend = 'pki-test'; this.backend = 'pki-test';
this.secretMountPath = this.owner.lookup('service:secret-mount-path'); this.secretMountPath = this.owner.lookup('service:secret-mount-path');
this.secretMountPath.currentPath = this.backend; this.secretMountPath.currentPath = this.backend;
@ -24,7 +24,7 @@ module('Integration | Component | pki issuer import', function (hooks) {
assert.expect(3); assert.expect(3);
await render( await render(
hbs` hbs`
<PkiCaCertificateImport <PkiImportPemBundle
@model={{this.model}} @model={{this.model}}
@onCancel={{this.onCancel}} @onCancel={{this.onCancel}}
@onSave={{this.onSave}} @onSave={{this.onSave}}
@ -33,7 +33,7 @@ module('Integration | Component | pki issuer import', function (hooks) {
{ owner: this.engine } { owner: this.engine }
); );
assert.dom('[data-test-pki-ca-cert-import-form]').exists('renders form'); assert.dom('[data-test-pki-import-pem-bundle-form]').exists('renders form');
assert.dom('[data-test-component="text-file"]').exists('renders text file input'); assert.dom('[data-test-component="text-file"]').exists('renders text file input');
await click('[data-test-text-toggle]'); await click('[data-test-text-toggle]');
await fillIn('[data-test-text-file-textarea]', this.pemBundle); await fillIn('[data-test-text-file-textarea]', this.pemBundle);
@ -41,7 +41,7 @@ module('Integration | Component | pki issuer import', function (hooks) {
}); });
test('it sends correct payload to import endpoint', async function (assert) { test('it sends correct payload to import endpoint', async function (assert) {
assert.expect(3); assert.expect(4);
this.server.post(`/${this.backend}/issuers/import/bundle`, (schema, req) => { this.server.post(`/${this.backend}/issuers/import/bundle`, (schema, req) => {
assert.ok(true, 'Request made to the correct endpoint to import issuer'); assert.ok(true, 'Request made to the correct endpoint to import issuer');
const request = JSON.parse(req.requestBody); const request = JSON.parse(req.requestBody);
@ -59,11 +59,11 @@ module('Integration | Component | pki issuer import', function (hooks) {
await render( await render(
hbs` hbs`
<PkiCaCertificateImport <PkiImportPemBundle
@model={{this.model}} @model={{this.model}}
@onCancel={{this.onCancel}} @onCancel={{this.onCancel}}
@onSave={{this.onSave}} @onSave={{this.onSave}}
@adapterOptions={{hash import=true}} @adapterOptions={{hash actionType="import" useIssuer=true}}
/> />
`, `,
{ owner: this.engine } { owner: this.engine }
@ -72,7 +72,42 @@ module('Integration | Component | pki issuer import', function (hooks) {
await click('[data-test-text-toggle]'); await click('[data-test-text-toggle]');
await fillIn('[data-test-text-file-textarea]', this.pemBundle); await fillIn('[data-test-text-file-textarea]', this.pemBundle);
assert.strictEqual(this.model.pemBundle, this.pemBundle); assert.strictEqual(this.model.pemBundle, this.pemBundle);
await click('[data-test-pki-ca-cert-import]'); await click('[data-test-pki-import-pem-bundle]');
});
test('it hits correct endpoint when userIssuer=false', async function (assert) {
assert.expect(4);
this.server.post(`${this.backend}/config/ca`, (schema, req) => {
assert.ok(true, 'Request made to the correct endpoint to import issuer');
const request = JSON.parse(req.requestBody);
assert.propEqual(
request,
{
pem_bundle: `${this.pemBundle}`,
},
'sends params in correct type'
);
return {};
});
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
await render(
hbs`
<PkiImportPemBundle
@model={{this.model}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
@adapterOptions={{hash actionType="import" useIssuer=false}}
/>
`,
{ owner: this.engine }
);
await click('[data-test-text-toggle]');
await fillIn('[data-test-text-file-textarea]', this.pemBundle);
assert.strictEqual(this.model.pemBundle, this.pemBundle);
await click('[data-test-pki-import-pem-bundle]');
}); });
test('it should unload record on cancel', async function (assert) { test('it should unload record on cancel', async function (assert) {
@ -80,7 +115,7 @@ module('Integration | Component | pki issuer import', function (hooks) {
this.onCancel = () => assert.ok(true, 'onCancel callback fires'); this.onCancel = () => assert.ok(true, 'onCancel callback fires');
await render( await render(
hbs` hbs`
<PkiCaCertificateImport <PkiImportPemBundle
@model={{this.model}} @model={{this.model}}
@onCancel={{this.onCancel}} @onCancel={{this.onCancel}}
@onSave={{this.onSave}} @onSave={{this.onSave}}

View File

@ -225,4 +225,26 @@ module('Unit | Adapter | pki/action', function (hooks) {
.save(adapterOptions); .save(adapterOptions);
}); });
}); });
module('actionType sign-intermediate', function () {
test('it overrides backend when adapter options specify a mount', async function (assert) {
assert.expect(1);
const mount = 'foo';
const issuerRef = 'ref';
const adapterOptions = {
adapterOptions: { actionType: 'sign-intermediate', mount, issuerRef },
};
this.server.post(`${mount}/issuer/${issuerRef}/sign-intermediate`, () => {
assert.ok(true, 'request made to correct mount');
return {};
});
await this.store
.createRecord('pki/action', {
csr: '---BEGIN REQUEST---',
})
.save(adapterOptions);
});
});
}); });