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) {
const { issuerId } = snapshot.record;
const backend = this._getBackend(snapshot);

View File

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

View File

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

View File

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

View File

@ -6,17 +6,16 @@ import { task } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
import { waitFor } from '@ember/test-waiters';
import errorMessage from 'vault/utils/error-message';
import PkiIssuerModel from 'vault/models/pki/issuer';
import PkiActionModel from 'vault/models/pki/action';
/**
* @module PkiCaCertificateImport
* PkiCaCertificateImport components are used to import PKI CA certificates and keys via pem_bundle.
* @module PkiImportPemBundle
* 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
*
* @example
* ```js
* <PkiCaCertificateImport @model={{this.model}} />
* <PkiImportPemBundle @model={{this.model}} />
* ```
*
* @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.
*/
interface AdapterOptions {
actionType: string;
useIssuer: boolean | undefined;
}
interface Args {
onSave: CallableFunction;
onCancel: CallableFunction;
model: PkiIssuerModel | PkiActionModel;
adapterOptions: object | undefined;
model: PkiActionModel;
adapterOptions: AdapterOptions;
}
export default class PkiCaCertificateImport extends Component<Args> {
export default class PkiImportPemBundle extends Component<Args> {
@service declare readonly flashMessages: FlashMessageService;
@tracked errorBanner = '';
@ -42,7 +45,7 @@ export default class PkiCaCertificateImport extends Component<Args> {
event.preventDefault();
try {
yield this.args.model.save({ adapterOptions: this.args.adapterOptions });
this.flashMessages.success('Successfully imported certificate.');
this.flashMessages.success('Successfully imported data.');
this.args.onSave();
} catch (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
// as they'd like.
const issuerId = await this.store
.createRecord('pki/issuer', { pemBundle: signedCaChain })
.save({ adapterOptions: { import: true, mount: intMount } })
.createRecord('pki/action', { pemBundle: signedCaChain })
.save({ adapterOptions: { actionType: 'import', mount: intMount, useIssuer: true } })
.then((importedIssuer) => {
return Object.keys(importedIssuer.mapping).find(
// matching key is the issuer_id

View File

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

View File

@ -8,10 +8,9 @@
</h1>
</p.levelLeft>
</PageHeader>
<PkiCaCertificateImport
<PkiImportPemBundle
@model={{this.model}}
@onCancel={{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 click(SELECTORS.issuersTab);
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);
issuers = this.store.peekAll('pki/issuer');
assert.strictEqual(issuers.length, 1, 'Issuer model created');
issuers = this.store.peekAll('pki/action');
assert.strictEqual(issuers.length, 1, 'Action model created');
const issuer = issuers.objectAt(0);
assert.true(issuer.hasDirtyAttributes, 'Issuer has dirty attrs');
assert.true(issuer.isNew, 'Issuer is new');
assert.true(issuer.hasDirtyAttributes, 'Action has dirty attrs');
assert.true(issuer.isNew, 'Action is new');
// Exit
await click('[data-test-pki-ca-cert-cancel]');
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/issuers`);
issuers = this.store.peekAll('pki/issuer');
assert.strictEqual(issuers.length, 0, 'Issuer is removed from store');
issuers = this.store.peekAll('pki/action');
assert.strictEqual(issuers.length, 0, 'Action is removed from store');
});
test('import issuer exit via breadcrumb', async function (assert) {
let issuers;
@ -184,15 +184,15 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
issuers = this.store.peekAll('pki/issuer');
assert.strictEqual(issuers.length, 0, 'No issuers exist yet');
await click(SELECTORS.importIssuerLink);
issuers = this.store.peekAll('pki/issuer');
assert.strictEqual(issuers.length, 1, 'Issuer model created');
issuers = this.store.peekAll('pki/action');
assert.strictEqual(issuers.length, 1, 'Action model created');
const issuer = issuers.objectAt(0);
assert.true(issuer.hasDirtyAttributes, 'Issuer has dirty attrs');
assert.true(issuer.isNew, 'Issuer is new');
assert.true(issuer.hasDirtyAttributes, 'Action model has dirty attrs');
assert.true(issuer.isNew, 'Action model is new');
// Exit
await click(SELECTORS.overviewBreadcrumb);
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');
});
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();
await click('[data-test-text-toggle]');
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(
currentURL(),
`/vault/secrets/${this.mountPath}/pki/issuers`,

View File

@ -9,7 +9,7 @@ export const SELECTORS = {
// pki-generate-root
...GENERATE_ROOT,
// pki-ca-cert-import
importForm: '[data-test-pki-ca-cert-import-form]',
importForm: '[data-test-pki-import-pem-bundle-form]',
// generate-intermediate
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 { issuerPemBundle } from 'vault/tests/helpers/pki/values';
module('Integration | Component | pki issuer import', function (hooks) {
module('Integration | Component | PkiImportPemBundle', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
setupEngine(hooks, 'pki'); // https://github.com/ember-engines/ember-engines/pull/653
hooks.beforeEach(function () {
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.secretMountPath = this.owner.lookup('service:secret-mount-path');
this.secretMountPath.currentPath = this.backend;
@ -24,7 +24,7 @@ module('Integration | Component | pki issuer import', function (hooks) {
assert.expect(3);
await render(
hbs`
<PkiCaCertificateImport
<PkiImportPemBundle
@model={{this.model}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
@ -33,7 +33,7 @@ module('Integration | Component | pki issuer import', function (hooks) {
{ 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');
await click('[data-test-text-toggle]');
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) {
assert.expect(3);
assert.expect(4);
this.server.post(`/${this.backend}/issuers/import/bundle`, (schema, req) => {
assert.ok(true, 'Request made to the correct endpoint to import issuer');
const request = JSON.parse(req.requestBody);
@ -59,11 +59,11 @@ module('Integration | Component | pki issuer import', function (hooks) {
await render(
hbs`
<PkiCaCertificateImport
<PkiImportPemBundle
@model={{this.model}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
@adapterOptions={{hash import=true}}
@adapterOptions={{hash actionType="import" useIssuer=true}}
/>
`,
{ owner: this.engine }
@ -72,7 +72,42 @@ module('Integration | Component | pki issuer import', function (hooks) {
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-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) {
@ -80,7 +115,7 @@ module('Integration | Component | pki issuer import', function (hooks) {
this.onCancel = () => assert.ok(true, 'onCancel callback fires');
await render(
hbs`
<PkiCaCertificateImport
<PkiImportPemBundle
@model={{this.model}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}

View File

@ -225,4 +225,26 @@ module('Unit | Adapter | pki/action', function (hooks) {
.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);
});
});
});