UI: PKI config via import (#18504)
This commit is contained in:
parent
0635d304de
commit
c5eacf789a
|
@ -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`;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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.'));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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." />
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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', {})
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -1,6 +1,12 @@
|
|||
/**
|
||||
* Catch-all for ember-data.
|
||||
*/
|
||||
export default interface ModelRegistry {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// Type that comes back from expandAttributeMeta
|
||||
export interface FormField {
|
||||
name: string;
|
||||
type: string;
|
||||
options: unknown;
|
||||
}
|
|
@ -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[];
|
||||
}[];
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue