UI: PKI config via import (#18504)

This commit is contained in:
Chelsea Shaw 2023-01-04 12:18:55 -06:00 committed by GitHub
parent 0635d304de
commit c5eacf789a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 214 additions and 27 deletions

View File

@ -0,0 +1,14 @@
import { encodePath } from 'vault/utils/path-encoding-helpers';
import ApplicationAdapter from '../../application';
export default class PkiConfigImportAdapter extends ApplicationAdapter {
namespace = 'v1';
urlForCreateRecord(modelName, snapshot) {
const { backend } = snapshot.record;
if (!backend) {
throw new Error('URL for create record is missing required attributes');
}
return `${this.buildURL()}/${encodePath(backend)}/issuers/import/bundle`;
}
}

View File

@ -0,0 +1,14 @@
import Model, { attr } from '@ember-data/model';
import { inject as service } from '@ember/service';
export default class PkiConfigImportModel extends Model {
@service secretMountPath;
@attr('string') pemBundle;
@attr importedIssuers;
@attr importedKeys;
get backend() {
return this.secretMountPath.currentPath;
}
}

View File

@ -59,7 +59,11 @@
</InfoTableRow>
{{else if (eq attr.name "keyId")}}
<InfoTableRow @label={{or attr.options.label (humanize (dasherize attr.name))}} @value={{get @issuer attr.name}}>
<LinkTo @route="keys.key" @model={{get @issuer attr.name}}>{{get @issuer attr.name}}</LinkTo>
{{#if @issuer.keyId}}
<LinkTo @route="keys.key" @model={{@issuer.keyId}}>{{@issuer.keyId}}</LinkTo>
{{else}}
<Icon @name="minus" />
{{/if}}
</InfoTableRow>
{{else}}
<InfoTableRow

View File

@ -0,0 +1,14 @@
<form {{on "submit" this.submitForm}} data-test-pki-config-import-form>
<TextFile @subText="Upload a PEM bundle or certificate" @onChange={{this.onFileUploaded}} @label="Import" />
{{yield}}
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="control">
<button type="submit" class="button is-primary" data-test-pki-config-save>
Done
</button>
<LinkTo @route="overview" class="button has-left-margin-s" data-test-pki-config-cancel>
Cancel
</LinkTo>
</div>
</div>
</form>

View File

@ -0,0 +1,63 @@
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
// Types
import { HTMLElementEvent } from 'forms';
import PkiConfigImportModel from 'vault/models/pki/config/import';
import Store from '@ember-data/store';
import Router from '@ember/routing/router';
import FlashMessageService from 'vault/services/flash-messages';
import errorMessage from 'vault/utils/error-message';
interface File {
value: string;
fileName?: string;
enterAsText: boolean;
}
/**
* Pki Config Import Component creates a PKI Config record on mount, and cleans it up if dirty on unmount
*/
export default class PkiConfigImportComponent extends Component<Record<string, never>> {
@service declare readonly store: Store;
@service declare readonly router: Router;
@service declare readonly flashMessages: FlashMessageService;
@tracked configModel: PkiConfigImportModel | null = null;
constructor(owner: unknown, args: Record<string, never>) {
super(owner, args);
const model = this.store.createRecord('pki/config/import');
this.configModel = model;
}
willDestroy() {
super.willDestroy();
const config = this.configModel;
// error is thrown when you attempt to unload a record that is inFlight (isSaving)
if ((config?.isNew || config?.hasDirtyAttributes) && !config?.isSaving) {
config.unloadRecord();
}
}
@action
onFileUploaded(file: File) {
if (!this.configModel) return;
this.configModel.pemBundle = file.value;
}
@action submitForm(evt: HTMLElementEvent<HTMLFormElement>) {
evt.preventDefault();
if (!this.configModel) return;
this.configModel
.save()
.then(() => {
this.flashMessages.success('Successfully imported the certificate.');
this.router.transitionTo('vault.cluster.secrets.backend.pki.issuers.index');
})
.catch((e) => {
this.flashMessages.danger(errorMessage(e, 'Could not import the given certificate.'));
});
}
}

View File

@ -27,7 +27,9 @@
</div>
{{/each}}
</div>
{{#if this.configType}}
{{#if (eq this.configType "import")}}
<PkiConfig::Import />
{{else if this.configType}}
{{! TODO: Forms }}
{{else}}
<EmptyState @title="Choose an option" @message="To see configuration options, choose your desired output above." />

View File

@ -8,6 +8,7 @@ import { tracked } from '@glimmer/tracking';
import errorMessage from 'vault/utils/error-message';
import FlashMessageService from 'vault/services/flash-messages';
import DownloadService from 'vault/services/download';
import PkiCertificateGenerateModel from 'vault/models/pki/certificate/generate';
interface Args {
onSuccess: CallableFunction;
@ -15,26 +16,6 @@ interface Args {
type: string;
}
interface PkiCertificateGenerateModel {
name: string;
backend: string;
serialNumber: string;
certificate: string;
formFields: FormField[];
formFieldsGroup: {
[k: string]: FormField[];
}[];
save: () => void;
rollbackAttributes: () => void;
unloadRecord: () => void;
destroyRecord: () => void;
canRevoke: boolean;
}
interface FormField {
name: string;
type: string;
options: unknown;
}
export default class PkiRoleGenerate extends Component<Args> {
@service declare readonly router: Router;
@service declare readonly store: Store;

View File

@ -12,6 +12,8 @@ export default class PkiOverviewRoute extends Route {
}
hasConfig() {
// When the engine is configured, it creates a default issuer.
// If the issuers list is empty, we know it hasn't been configured
const endpoint = `${this.win.origin}/v1/${this.secretMountPath.currentPath}/issuers?list=true`;
return this.auth
.ajax(endpoint, 'GET', {})

View File

@ -0,0 +1,18 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupEngine } from 'ember-engines/test-support';
module('Integration | Component | pki-config/import', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'pki');
test('it renders', async function (assert) {
await render(hbs`<PkiConfig::Import />`, { owner: this.engine });
assert.dom('[data-test-pki-config-import-form]').exists({ count: 1 }, 'Import form exists');
assert.dom('[data-test-pki-config-save]').isNotDisabled('Save button not disabled');
assert.dom('[data-test-pki-config-cancel]').exists({ count: 1 }, 'cancel button exists');
});
});

View File

@ -0,0 +1,41 @@
import { module, test } from 'qunit';
import { setupTest } from 'vault/tests/helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Adapter | pki/config/import', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
this.backend = 'pki-test';
this.secretMountPath.currentPath = this.backend;
});
test('it exists', function (assert) {
const adapter = this.owner.lookup('adapter:pki/config/import');
assert.ok(adapter);
});
test('it should make request to correct endpoint on create', async function (assert) {
assert.expect(2);
this.server.post(`${this.backend}/issuers/import/bundle`, (url, { requestBody }) => {
assert.ok(true, `request made to correct endpoint ${url}`);
assert.deepEqual(
JSON.parse(requestBody),
{ pem_bundle: 'abcdefg' },
'has correct payload for config/ca'
);
return {
data: {},
};
});
await this.store
.createRecord('pki/config/import', {
pemBundle: 'abcdefg',
})
.save();
});
});

View File

@ -1,6 +1,12 @@
/**
* Catch-all for ember-data.
*/
export default interface ModelRegistry {
[key: string]: any;
import Model from '@ember-data/model';
import PkiCertificateGenerateModel from 'vault/models/pki/certificate/generate';
import PkiConfigImportModel from 'vault/models/pki/config/import';
declare module 'ember-data/types/registries/model' {
export default interface ModelRegistry {
'pki/config/import': PkiConfigImportModel;
'pki/certificate/generate': PkiCertificateGenerateModel;
// Catchall for any other models
[key: string]: any;
}
}

View File

@ -0,0 +1,6 @@
// Type that comes back from expandAttributeMeta
export interface FormField {
name: string;
type: string;
options: unknown;
}

View File

@ -0,0 +1,13 @@
import Model from '@ember-data/model';
import { FormField } from 'vault/app-types';
export default class PkiCertificateGenerateModel extends Model {
name: string;
backend: string;
serialNumber: string;
certificate: string;
formFields: FormField[];
formFieldsGroup: {
[k: string]: FormField[];
}[];
}

View File

@ -0,0 +1,9 @@
import Model from '@ember-data/model';
export default class PkiConfigImportModel extends Model {
backend: string;
secretMountPath: unknown;
importFile: string;
pemBundle: string;
certificate: string;
}