UI: PKI error handling + some small bugs! (#20478)
This commit is contained in:
parent
3ff2b011ab
commit
650743e18f
|
@ -6,7 +6,7 @@
|
|||
import ApplicationAdapter from '../application';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
import { all } from 'rsvp';
|
||||
import { verifyCertificates } from 'vault/utils/parse-pki-cert';
|
||||
import { verifyCertificates, parseCertificate } from 'vault/utils/parse-pki-cert';
|
||||
|
||||
export default class PkiIssuerAdapter extends ApplicationAdapter {
|
||||
namespace = 'v1';
|
||||
|
@ -39,7 +39,13 @@ export default class PkiIssuerAdapter extends ApplicationAdapter {
|
|||
const issuerRecord = await this.queryRecord(store, type, { id, backend: query.backend });
|
||||
const { data } = issuerRecord;
|
||||
const isRoot = await verifyCertificates(data.certificate, data.certificate);
|
||||
return { ...keyInfo, ...data, isRoot };
|
||||
const parsedCertificate = parseCertificate(data.certificate);
|
||||
return {
|
||||
...keyInfo,
|
||||
...data,
|
||||
isRoot,
|
||||
parsedCertificate: { common_name: parsedCertificate.common_name },
|
||||
};
|
||||
} catch (e) {
|
||||
return { ...keyInfo, issuer_id: id };
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@
|
|||
border: 1px solid $grey-light;
|
||||
}
|
||||
|
||||
&.is-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&.is-small {
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
@ -1,264 +0,0 @@
|
|||
{{#if this.replaceCA}}
|
||||
<MessageError @model={{this.model}} />
|
||||
<h2 data-test-title class="title is-3">
|
||||
{{#if this.needsConfig}}
|
||||
Configure CA Certificate
|
||||
{{else}}
|
||||
{{#if this.model.certificate}}
|
||||
Generated Certificate
|
||||
{{else}}
|
||||
Add CA Certificate
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</h2>
|
||||
{{#if (or this.model.certificate this.model.csr)}}
|
||||
{{#if (eq this.model.canParse false)}}
|
||||
<AlertBanner
|
||||
@type="info"
|
||||
@message="There was an error parsing the certificate's metadata. As a result, Vault cannot display the issue and expiration dates. This will not interfere with the certificate's functionality."
|
||||
data-test-warning
|
||||
/>
|
||||
{{/if}}
|
||||
{{#each this.model.attrs as |attr|}}
|
||||
{{#if (and attr.options.masked (get this.model attr.name))}}
|
||||
<InfoTableRow
|
||||
data-test-table-row
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get this.model attr.name}}
|
||||
>
|
||||
<MaskedInput @value={{get this.model attr.name}} @displayOnly={{true}} @allowCopy={{true}} />
|
||||
</InfoTableRow>
|
||||
{{else if (and (get this.model attr.name) (or (eq attr.name "issueDate") (eq attr.name "expiryDate")))}}
|
||||
<InfoTableRow
|
||||
data-test-table-row={{this.value}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{date-format (get this.model attr.name) "MMM dd, yyyy hh:mm:ss a" isFormatted=true}}
|
||||
/>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
data-test-table-row={{this.value}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get this.model attr.name}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<CopyButton
|
||||
@clipboardText={{or this.model.certificate this.model.csr}}
|
||||
@class="button"
|
||||
@buttonType="button"
|
||||
@success={{action (set-flash-message (concat (if this.model.certificate "Certificate" "CSR") " copied!"))}}
|
||||
>
|
||||
Copy
|
||||
{{if this.model.certificate "Certificate" "CSR"}}
|
||||
</CopyButton>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button data-test-back-button {{action "refresh"}} type="button" class="button">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<form {{action "saveCA" on="submit"}} aria-label="ca form" data-test-generate-root-cert="true">
|
||||
<NamespaceReminder @mode="save" @noun="PKI change" />
|
||||
<FormFieldGroupsLoop @model={{this.model}} @mode={{this.mode}} />
|
||||
<div class="field is-grouped is-grouped-split box is-fullwidth is-bottomless">
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button
|
||||
data-test-submit
|
||||
type="submit"
|
||||
class="button is-primary {{if this.loading 'is-loading'}}"
|
||||
disabled={{this.loading}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button data-test-back-button {{action "toggleReplaceCA"}} type="button" class="button">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
{{else if this.signIntermediate}}
|
||||
{{#if (or this.model.certificate)}}
|
||||
<AlertBanner
|
||||
@type="warning"
|
||||
@message="If using this for an Intermediate CA in Vault, copy the certificate below and write it to the PKI mount being used as an intermediate using the `Set signed Intermediate` endpoint."
|
||||
data-test-warning
|
||||
/>
|
||||
{{#each this.model.attrs as |attr|}}
|
||||
{{#if (and attr.options.masked (get this.model attr.name))}}
|
||||
<InfoTableRow
|
||||
data-test-table-row={{this.value}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get this.model attr.name}}
|
||||
>
|
||||
<MaskedInput @value={{get this.model attr.name}} @displayOnly={{true}} @allowCopy={{true}} />
|
||||
</InfoTableRow>
|
||||
{{else if (and (get this.model attr.name) (or (eq attr.name "issueDate") (eq attr.name "expiryDate")))}}
|
||||
<InfoTableRow
|
||||
data-test-table-row={{this.value}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{date-format (get this.model attr.name) "MMM dd, yyyy hh:mm:ss a" isFormatted=true}}
|
||||
/>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
data-test-table-row={{this.value}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get this.model attr.name}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<CopyButton
|
||||
@clipboardText={{this.model.certificate}}
|
||||
@class="button"
|
||||
@buttonType="button"
|
||||
@success={{action (set-flash-message "Certificate copied!")}}
|
||||
>
|
||||
Copy Certificate
|
||||
</CopyButton>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button data-test-back-button {{action "refresh"}} type="button" class="button">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<h2 data-test-title class="title is-3">Sign intermediate</h2>
|
||||
<NamespaceReminder @mode="save" @noun="PKI change" />
|
||||
<MessageError @model={{this.model}} @errors={{this.errors}} />
|
||||
<form {{action "saveCA" on="submit"}} data-test-sign-intermediate-form="true" aria-label="sign intermediate form">
|
||||
<FormFieldGroupsLoop @model={{this.model}} @mode={{this.mode}} />
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button
|
||||
data-test-submit
|
||||
type="submit"
|
||||
class="button is-primary {{if this.loading 'is-loading'}}"
|
||||
disabled={{this.loading}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button {{action "toggleVal" "signIntermediate" false}} type="button" class="button">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
{{else if this.setSignedIntermediate}}
|
||||
<h2 data-test-title class="title is-3">Set signed intermediate</h2>
|
||||
<NamespaceReminder @mode="save" @noun="PKI change" />
|
||||
<MessageError @model={{this.model}} />
|
||||
<p class="has-text-grey-dark">
|
||||
Submit a signed CA certificate corresponding to a generated private key.
|
||||
</p>
|
||||
<form
|
||||
{{action "saveCA" "setSignedIntermediate" on="submit"}}
|
||||
aria-label="set signed intermediate form"
|
||||
data-test-set-signed-intermediate-form="true"
|
||||
>
|
||||
<div class="field">
|
||||
<label for="certificate" class="is-label">
|
||||
Signed Intermediate Certificate
|
||||
</label>
|
||||
<div class="control">
|
||||
<Textarea
|
||||
data-test-signed-intermediate
|
||||
class="textarea"
|
||||
id="certificate"
|
||||
name="certificate"
|
||||
@value={{this.model.certificate}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button
|
||||
data-test-submit
|
||||
type="submit"
|
||||
class="button is-primary {{if this.loading 'is-loading'}}"
|
||||
disabled={{this.loading}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button data-test-back-button {{action "toggleVal" "setSignedIntermediate" false}} type="button" class="button">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{else}}
|
||||
<p class="has-text-grey-dark">
|
||||
This is the default CA certificate used in Vault. It is not used for self-signed certificates or if you have a signed
|
||||
intermediate CA certificate with a generated key.
|
||||
</p>
|
||||
{{#each this.downloadHrefs as |dl|}}
|
||||
<div class="box is-shadowless is-marginless is-fullwidth has-slim-padding">
|
||||
<ExternalLink @href={{dl.url}} @sameTab={{true}} download={{dl.name}} data-test-ca-download-link>
|
||||
{{dl.display}}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<div class="field is-grouped box is-fullwidth is-shadowless">
|
||||
<div class="control">
|
||||
<button data-test-go-replace-ca type="button" {{action "toggleReplaceCA"}} class="button">
|
||||
{{#if this.needsConfig}}
|
||||
Configure CA
|
||||
{{else}}
|
||||
Add CA
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
{{#if this.config.pem}}
|
||||
<div class="control">
|
||||
<button data-test-go-sign-intermediate type="button" {{action "toggleVal" "signIntermediate"}} class="button">
|
||||
Sign intermediate
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control">
|
||||
<button
|
||||
data-test-go-set-signed-intermediate
|
||||
type="button"
|
||||
{{action "toggleVal" "setSignedIntermediate"}}
|
||||
class="button"
|
||||
>
|
||||
Set signed intermediate
|
||||
</button>
|
||||
</div>
|
||||
{{#unless this.needsConfig}}
|
||||
<div class="control">
|
||||
<ToolTip @verticalPosition="above" @horizontalPosition="center" as |T|>
|
||||
<T.Trigger data-test-tooltip-trigger tabindex="-1">
|
||||
<button type="button" class="button is-primary" disabled={{true}}>
|
||||
Delete
|
||||
</button>
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip smaller-font">
|
||||
<div class="box" data-test-hover-copy-tooltip-text>
|
||||
Deleting a CA is only available via the CLI and API.
|
||||
<DocLink @path="/vault/api-docs/secret/pki#delete-issuer" class="doc-link-subtle">
|
||||
Learn more
|
||||
</DocLink>
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{/if}}
|
|
@ -22,16 +22,20 @@
|
|||
}}</span>
|
||||
{{/if}}
|
||||
{{#if pkiIssuer.serialNumber}}
|
||||
<InfoTooltip>
|
||||
Serial number
|
||||
</InfoTooltip>
|
||||
<span class="tag has-background-transparent" data-test-serial-number={{idx}}>{{pkiIssuer.serialNumber}}</span>
|
||||
<span class="tag is-transparent has-right-margin-none" data-test-serial-number={{idx}}>
|
||||
<InfoTooltip>
|
||||
Serial number
|
||||
</InfoTooltip>
|
||||
{{pkiIssuer.serialNumber}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{#if pkiIssuer.keyId}}
|
||||
<InfoTooltip>
|
||||
Key ID
|
||||
</InfoTooltip>
|
||||
<span class="tag has-background-transparent" data-test-key-id={{idx}}>{{pkiIssuer.keyId}}</span>
|
||||
{{#if pkiIssuer.parsedCertificate.common_name}}
|
||||
<span class="tag is-transparent has-left-margin-none" data-test-common-name={{idx}}>
|
||||
<InfoTooltip>
|
||||
Common name
|
||||
</InfoTooltip>
|
||||
{{pkiIssuer.parsedCertificate.common_name}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{{! DEFAULT VIEW }}
|
||||
{{#if (eq group "default")}}
|
||||
{{#each fields as |attr|}}
|
||||
{{#if (eq attr.name "issuerRef")}}
|
||||
{{#if (and (eq attr.name "issuerRef") @issuers)}}
|
||||
<div class="has-top-margin-m {{unless this.showDefaultIssuer 'has-bottom-margin-xs' 'has-bottom-margin-m'}}">
|
||||
<FormFieldLabel
|
||||
for={{attr.name}}
|
||||
|
|
|
@ -19,8 +19,9 @@ export default class PkiRolesErrorRoute extends Route {
|
|||
{ label: 'Overview', route: 'overview' },
|
||||
{ label: 'Roles', route: 'roles.index' },
|
||||
{ label: 'Issuers', route: 'issuers.index' },
|
||||
{ label: 'Certificates', route: 'certificates.index' },
|
||||
{ label: 'Keys', route: 'keys.index' },
|
||||
{ label: 'Certificates', route: 'certificates.index' },
|
||||
{ label: 'Configuration', route: 'configuration.index' },
|
||||
];
|
||||
controller.title = this.secretMountPath.currentPath;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,13 @@ export default class PkiRolesCreateRoute extends Route {
|
|||
const backend = this.secretMountPath.currentPath;
|
||||
return hash({
|
||||
role: this.store.createRecord('pki/role', { backend }),
|
||||
issuers: this.store.query('pki/issuer', { backend }),
|
||||
issuers: this.store.query('pki/issuer', { backend }).catch((err) => {
|
||||
if (err.httpStatus === 404) {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,13 @@ export default class PkiRoleEditRoute extends Route {
|
|||
backend,
|
||||
id: role,
|
||||
}),
|
||||
issuers: this.store.query('pki/issuer', { backend }),
|
||||
issuers: this.store.query('pki/issuer', { backend }).catch((err) => {
|
||||
if (err.httpStatus === 404) {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -355,7 +355,7 @@ module('Acceptance | pki workflow', function (hooks) {
|
|||
assert.dom('.linked-block').exists({ count: 1 }, 'One issuer is in list');
|
||||
assert.dom('[data-test-is-root-tag="0"]').hasText('root');
|
||||
assert.dom('[data-test-serial-number="0"]').exists({ count: 1 }, 'displays serial number tag');
|
||||
assert.dom('[data-test-key-id="0"]').exists({ count: 1 }, 'displays key id tag');
|
||||
assert.dom('[data-test-common-name="0"]').exists({ count: 1 }, 'displays cert common name tag');
|
||||
});
|
||||
test('lists the correct issuer metadata info when user has only read permission', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
@ -379,7 +379,7 @@ module('Acceptance | pki workflow', function (hooks) {
|
|||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await click(SELECTORS.issuersTab);
|
||||
assert.dom('[data-test-serial-number="0"]').exists({ count: 1 }, 'displays serial number tag');
|
||||
assert.dom('[data-test-key-id="0"]').exists({ count: 1 }, 'displays key id tag');
|
||||
assert.dom('[data-test-common-name="0"]').exists({ count: 1 }, 'displays cert common name tag');
|
||||
});
|
||||
|
||||
test('details view renders correct number of info items', async function (assert) {
|
||||
|
|
Loading…
Reference in New Issue