diff --git a/ui/app/models/pki/action.js b/ui/app/models/pki/action.js
index 07160545e..ddfefdb13 100644
--- a/ui/app/models/pki/action.js
+++ b/ui/app/models/pki/action.js
@@ -1,5 +1,6 @@
import Model, { attr } from '@ember-data/model';
import { inject as service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import { withFormFields } from 'vault/decorators/model-form-fields';
import { withModelValidations } from 'vault/decorators/model-validations';
@@ -10,7 +11,7 @@ const validations = {
issuerName: [
{
validator(model) {
- if (model.issuerName === 'default') return false;
+ if (model.actionType === 'generate-root' && model.issuerName === 'default') return false;
return true;
},
message: 'Issuer name must be unique across all issuers and not be the reserved value default.',
@@ -23,6 +24,8 @@ const validations = {
export default class PkiActionModel extends Model {
@service secretMountPath;
+ @tracked actionType; // used to toggle between different form fields when creating configuration
+
/* actionType import */
@attr('string') pemBundle;
@@ -33,7 +36,7 @@ export default class PkiActionModel extends Model {
})
type;
- @attr('string') issuerName; // REQUIRED, cannot be "default"
+ @attr('string') issuerName; // REQUIRED for generate-root actionType, cannot be "default"
@attr('string') keyName; // cannot be "default"
@@ -123,6 +126,11 @@ export default class PkiActionModel extends Model {
})
serialNumber;
+ @attr('boolean', {
+ subText: 'Whether to add a Basic Constraints extension with CA: true.',
+ })
+ addBasicConstraints;
+
@attr({
label: 'Backdate validity',
detailsLabel: 'Issued certificate backdating',
diff --git a/ui/app/serializers/pki/action.js b/ui/app/serializers/pki/action.js
index b4852fe9b..4978f43c2 100644
--- a/ui/app/serializers/pki/action.js
+++ b/ui/app/serializers/pki/action.js
@@ -25,35 +25,41 @@ export default class PkiActionSerializer extends ApplicationSerializer {
_allowedParamsByType(actionType, type) {
const keyFields = keyParamsByType(type).map((attrName) => underscore(attrName).toLowerCase());
+ const commonProps = [
+ 'alt_names',
+ 'common_name',
+ 'country',
+ 'exclude_cn_from_sans',
+ 'format',
+ 'ip_sans',
+ 'locality',
+ 'organization',
+ 'other_sans',
+ 'ou',
+ 'postal_code',
+ 'province',
+ 'serial_number',
+ 'street_address',
+ 'type',
+ 'uri_sans',
+ ...keyFields,
+ ];
switch (actionType) {
case 'import':
return ['pem_bundle'];
case 'generate-root':
return [
- 'alt_names',
- 'common_name',
- 'country',
- 'exclude_cn_from_sans',
- 'format',
- 'ip_sans',
+ ...commonProps,
'issuer_name',
- 'locality',
'max_path_length',
'not_after',
'not_before_duration',
- 'organization',
- 'other_sans',
- 'ou',
'permitted_dns_domains',
- 'postal_code',
'private_key_format',
- 'province',
- 'serial_number',
- 'street_address',
'ttl',
- 'type',
- ...keyFields,
];
+ case 'generate-csr':
+ return [...commonProps, 'add_basic_constraints'];
default:
// if type doesn't match, serialize all
return null;
diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs
index 3bdfe1494..50695e8f4 100644
--- a/ui/lib/core/addon/components/form-field.hbs
+++ b/ui/lib/core/addon/components/form-field.hbs
@@ -304,7 +304,7 @@
{{on "input" this.onChangeWithEvent}}
{{on "change" this.onChangeWithEvent}}
{{on "keyup" this.handleKeyUp}}
- class="input {{if this.validationError 'has-error-border'}} has-bottom-margin-m"
+ class="input {{if this.validationError 'has-error-border' 'has-bottom-margin-m'}}"
maxLength={{@attr.options.characterLimit}}
/>
{{#if @attr.options.validationAttr}}
diff --git a/ui/lib/pki/addon/components/pki-configure-form.hbs b/ui/lib/pki/addon/components/pki-configure-form.hbs
index 2ddfc8360..49bdaa49c 100644
--- a/ui/lib/pki/addon/components/pki-configure-form.hbs
+++ b/ui/lib/pki/addon/components/pki-configure-form.hbs
@@ -2,7 +2,7 @@
{{#each this.configTypes as |option|}}
-
- {{#if (eq this.actionType "import")}}
+ {{#if (eq @config.actionType "import")}}
- {{else if (eq this.actionType "generate-root")}}
+ {{else if (eq @config.actionType "generate-root")}}
+ {{else if (eq @config.actionType "generate-csr")}}
+
- {{else if (eq this.actionType "generate-csr")}}
- POST /intermediate/generate/:type ~or~ /issuers/generate/intermediate/:type
{{else}}
{
@service declare readonly store: Store;
@service declare readonly router: Router;
@service declare readonly flashMessages: FlashMessageService;
- @tracked actionType = '';
get configTypes() {
return [
@@ -51,24 +49,6 @@ export default class PkiConfigureForm extends Component {
];
}
- shouldUseIssuerEndpoint() {
- const { config } = this.args;
- // To determine which endpoint the config adapter should use,
- // we want to check capabilities on the newer endpoints (those
- // prefixed with "issuers") and use the old path as fallback
- // if user does not have permissions.
- switch (this.actionType) {
- case 'import':
- return config.canImportBundle;
- case 'generate-root':
- return config.canGenerateIssuerRoot;
- case 'generate-csr':
- return config.canGenerateIssuerIntermediate;
- default:
- return false;
- }
- }
-
@action cancel() {
this.args.config.rollbackAttributes();
this.router.transitionTo('vault.cluster.secrets.backend.pki.overview');
diff --git a/ui/lib/pki/addon/components/pki-generate-csr.hbs b/ui/lib/pki/addon/components/pki-generate-csr.hbs
new file mode 100644
index 000000000..b9af2e6fe
--- /dev/null
+++ b/ui/lib/pki/addon/components/pki-generate-csr.hbs
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/ui/lib/pki/addon/components/pki-generate-csr.ts b/ui/lib/pki/addon/components/pki-generate-csr.ts
new file mode 100644
index 000000000..736265f0b
--- /dev/null
+++ b/ui/lib/pki/addon/components/pki-generate-csr.ts
@@ -0,0 +1,76 @@
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+import { task } from 'ember-concurrency';
+import { waitFor } from '@ember/test-waiters';
+import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
+import FlashMessageService from 'vault/services/flash-messages';
+import PkiActionModel from 'vault/models/pki/action';
+import errorMessage from 'vault/utils/error-message';
+
+interface Args {
+ model: PkiActionModel;
+ useIssuer: boolean;
+ onSave: CallableFunction;
+ onCancel: CallableFunction;
+}
+
+export default class PkiGenerateIntermediateComponent extends Component {
+ @service declare readonly flashMessages: FlashMessageService;
+
+ @tracked modelValidations = null;
+ @tracked error: string | null = null;
+ @tracked alert: string | null = null;
+
+ formFields;
+
+ constructor(owner: unknown, args: Args) {
+ super(owner, args);
+ this.formFields = expandAttributeMeta(this.args.model, [
+ 'type',
+ 'commonName',
+ 'excludeCnFromSans',
+ 'format',
+ 'serialNumber',
+ 'addBasicConstraints',
+ ]);
+ }
+
+ @action
+ cancel() {
+ this.args.model.unloadRecord();
+ this.args.onCancel();
+ }
+
+ async getCapability(): Promise {
+ try {
+ const issuerCapabilities = await this.args.model.generateIssuerCsrPath;
+ return issuerCapabilities.get('canCreate') === true;
+ } catch (error) {
+ return false;
+ }
+ }
+
+ @task
+ @waitFor
+ *save(event: Event): Generator> {
+ event.preventDefault();
+ try {
+ const { model, onSave } = this.args;
+ const { isValid, state, invalidFormMessage } = model.validate();
+ if (isValid) {
+ const useIssuer = yield this.getCapability();
+ yield model.save({ adapterOptions: { actionType: 'generate-csr', useIssuer } });
+ this.flashMessages.success('Successfully generated CSR.');
+ onSave();
+ } else {
+ this.modelValidations = state;
+ this.alert = invalidFormMessage;
+ }
+ } catch (e) {
+ this.error = errorMessage(e);
+ this.alert = 'There was a problem generating the CSR.';
+ }
+ }
+}
diff --git a/ui/lib/pki/addon/components/pki-generate-root.hbs b/ui/lib/pki/addon/components/pki-generate-root.hbs
index e69204809..0dc6f7395 100644
--- a/ui/lib/pki/addon/components/pki-generate-root.hbs
+++ b/ui/lib/pki/addon/components/pki-generate-root.hbs
@@ -20,58 +20,7 @@
{{/let}}
{{/each}}
- {{! togglable groups }}
- {{#each-in this.groups as |group fields|}}
-
- {{#if (eq this.showGroup group)}}
-
- {{#if (eq group "Key parameters")}}
-
- {{#if (eq @model.type "internal")}}
- This certificate type is internal. This means that the private key will not be returned and cannot be retrieved
- later. Below, you will name the key and define its type and key bits.
- {{else if (eq @model.type "kms")}}
- This certificate type is kms, meaning managed keys will be used. Below, you will name the key and tell Vault
- where to find it in your KMS or HSM.
- Learn more about managed keys.
- {{else if (eq @model.type "exported")}}
- This certificate type is exported. This means the private key will be returned in the response. Below, you will
- name the key and define its type and key bits.
- {{else if (eq @model.type "existing")}}
- You chose to use an existing key. This means that we’ll use the key reference to create the CSR or root. Please
- provide the reference to the key.
- {{else}}
- Please choose a type to see key parameter options.
- {{/if}}
-
- {{#if (eq group "Subject Alternative Name (SAN) Options")}}
- SAN fields are an extension that allow you specify additional host names (sites, IP addresses, common names,
- etc.) to be protected by a single certificate.
- {{else if (eq group "Additional subject fields")}}
- These fields provide more information about the client to which the certificate belongs.
- {{/if}}
-