d8e36a3916
* make all alert banners type=danger * finish tests and adding selectors
174 lines
6.5 KiB
JavaScript
174 lines
6.5 KiB
JavaScript
import Component from '@glimmer/component';
|
||
import { action } from '@ember/object';
|
||
import { task } from 'ember-concurrency';
|
||
import { inject as service } from '@ember/service';
|
||
import { tracked } from '@glimmer/tracking';
|
||
import errorMessage from 'vault/utils/error-message';
|
||
import { waitFor } from '@ember/test-waiters';
|
||
import { parseCertificate } from 'vault/utils/parse-pki-cert';
|
||
/**
|
||
* @module PkiIssuerCrossSign
|
||
* PkiIssuerCrossSign components render from a parent issuer's details page to cross-sign an intermediate issuer (from a different mount).
|
||
* The component reads an existing intermediate issuer, cross-signs it with a parent issuer and imports the new
|
||
* issuer into the existing intermediate mount using three inputs from the user:
|
||
* intermediateMount (the mount path where the issuer to be cross signed lives)
|
||
* intermediateIssuer (the name of the intermediate issuer, located in the above mount)
|
||
* newCrossSignedIssuer (the name of the to-be-cross-signed, new issuer)
|
||
*
|
||
* The requests involved and how those inputs are used:
|
||
* 1. Read an existing intermediate issuer
|
||
* -> GET /:intermediateMount/issuer/:intermediateIssuer
|
||
* 2. Create a new CSR based on this existing issuer ID
|
||
* -> POST /:intermediateMount/intermediate/generate/existing
|
||
* 3. Sign it with the new parent issuer, minting a new certificate.
|
||
* -> POST /this.args.parentIssuer.backend/issuer/this.args.parentIssuer.issuerName/sign-intermediate
|
||
* 4. Import it back into the existing mount
|
||
* -> POST /:intermediateMount/issuers/import/bundle
|
||
* 5. Read the imported issuer
|
||
* -> GET /:intermediateMount/issuer/:issuer_id
|
||
* 6. Update this issuer with the newCrossSignedIssuer
|
||
* -> POST /:intermediateMount/issuer/:issuer_id
|
||
*
|
||
* @example
|
||
* ```js
|
||
* <PkiIssuerCrossSign @parentIssuer={{this.model}} />
|
||
* ```
|
||
* @param {object} parentIssuer - the model of the issuing certificate that will sign the issuer to-be cross-signed
|
||
*/
|
||
|
||
export default class PkiIssuerCrossSign extends Component {
|
||
@service store;
|
||
@tracked formData = [];
|
||
@tracked signedIssuers = [];
|
||
|
||
inputFields = [
|
||
{
|
||
label: 'Mount path',
|
||
key: 'intermediateMount',
|
||
placeholder: 'Mount path',
|
||
helpText: 'The mount in which your new certificate can be found.',
|
||
},
|
||
{
|
||
label: "Issuer's current name",
|
||
key: 'intermediateIssuer',
|
||
placeholder: 'Current issuer name',
|
||
helpText: 'The API name of the previous intermediate which was cross-signed.',
|
||
},
|
||
{
|
||
label: 'New issuer name',
|
||
key: 'newCrossSignedIssuer',
|
||
placeholder: 'Enter a new issuer name',
|
||
helpText: `This is your new issuer’s name in the API.`,
|
||
},
|
||
];
|
||
|
||
get statusCount() {
|
||
const error = this.signedIssuers.filter((issuer) => issuer.hasError).length;
|
||
const success = this.signedIssuers.length - error;
|
||
return `${success} successful, ${error} ${error === 1 ? 'error' : 'errors'}`;
|
||
}
|
||
|
||
@task
|
||
@waitFor
|
||
*submit(e) {
|
||
e.preventDefault();
|
||
this.signedIssuers = [];
|
||
|
||
// iterate through submitted data and cross-sign each certificate
|
||
for (let row = 0; row < this.formData.length; row++) {
|
||
const { intermediateMount, intermediateIssuer, newCrossSignedIssuer } = this.formData[row];
|
||
try {
|
||
// returns data from existing and newly cross-signed issuers
|
||
// { intermediateIssuer: existingIssuer, newCrossSignedIssuer: crossSignedIssuer, intermediateMount: intMount }
|
||
const data = yield this.crossSignIntermediate(
|
||
intermediateMount,
|
||
intermediateIssuer,
|
||
newCrossSignedIssuer
|
||
);
|
||
this.signedIssuers.addObject({ ...data, hasError: false });
|
||
} catch (error) {
|
||
this.signedIssuers.addObject({
|
||
...this.formData[row],
|
||
hasError: errorMessage(error),
|
||
hasUnsupportedParams: error.cause ? error.cause.map((e) => e.message).join(', ') : null,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
@action
|
||
async crossSignIntermediate(intMount, intName, newCrossSignedIssuer) {
|
||
// 1. Fetch issuer we want to sign
|
||
const existingIssuer = await this.store.queryRecord('pki/issuer', {
|
||
backend: intMount,
|
||
id: intName,
|
||
});
|
||
|
||
// Translate certificate values to API parameters to pass along: CSR -> Signed CSR -> Cross-Signed issuer
|
||
// some of these values do not apply to a CSR, but pass anyway. If there is any issue parsing the certificate,
|
||
// (ex. the certificate contains unsupported values) direct user to manually cross-sign via CLI
|
||
const certData = parseCertificate(existingIssuer.certificate);
|
||
if (certData.parsing_errors.length > 0) {
|
||
throw new Error('Certificate must be manually cross-signed using the CLI.', {
|
||
cause: certData.parsing_errors,
|
||
});
|
||
}
|
||
|
||
// 2. Create the new CSR
|
||
const newCsr = await this.store
|
||
.createRecord('pki/action', {
|
||
keyRef: existingIssuer.keyId,
|
||
commonName: existingIssuer.commonName,
|
||
type: 'existing',
|
||
...certData,
|
||
})
|
||
.save({
|
||
adapterOptions: { actionType: 'generate-csr', mount: intMount, useIssuer: false },
|
||
})
|
||
.then(({ csr }) => csr);
|
||
|
||
// 3. Sign newCSR with correct parent to create cross-signed cert
|
||
const signedCaChain = await this.store
|
||
.createRecord('pki/action', {
|
||
csr: newCsr,
|
||
commonName: existingIssuer.commonName,
|
||
...certData,
|
||
})
|
||
.save({
|
||
adapterOptions: {
|
||
actionType: 'sign-intermediate',
|
||
mount: this.args.parentIssuer.backend,
|
||
issuerName: this.args.parentIssuer.issuerName,
|
||
},
|
||
})
|
||
.then(({ caChain }) => caChain.join('\n'));
|
||
|
||
// 4. Import the newly cross-signed cert to become an issuer
|
||
const issuerId = await this.store
|
||
.createRecord('pki/issuer', { pemBundle: signedCaChain })
|
||
.save({ adapterOptions: { import: true, mount: intMount } })
|
||
.then((importedIssuer) => {
|
||
return Object.keys(importedIssuer.mapping).find(
|
||
// matching key is the issuer_id
|
||
(key) => importedIssuer.mapping[key] === existingIssuer.keyId
|
||
);
|
||
});
|
||
|
||
// 5. Fetch issuer imported above by issuer_id, name and save
|
||
const crossSignedIssuer = await this.store.queryRecord('pki/issuer', { backend: intMount, id: issuerId });
|
||
crossSignedIssuer.issuerName = newCrossSignedIssuer;
|
||
crossSignedIssuer.save({ adapterOptions: { mount: intMount } });
|
||
return {
|
||
intermediateIssuer: existingIssuer,
|
||
newCrossSignedIssuer: crossSignedIssuer,
|
||
intermediateMount: intMount,
|
||
};
|
||
}
|
||
|
||
@action
|
||
reset() {
|
||
this.signedIssuers = [];
|
||
this.formData = [];
|
||
}
|
||
}
|