UI: VAULT-14972 VAULT-13808 VAULT-12777 Remove pki beta, old pki, remove unused cert attributes (#20062)

This commit is contained in:
Kianna 2023-04-12 09:18:46 -07:00 committed by GitHub
parent 1b5d527521
commit e6b890f7ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 8 additions and 3061 deletions

View File

@ -1,13 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Adapter from './pki';
export default Adapter.extend({
url(_, snapshot) {
const backend = snapshot.attr('backend');
return `/v1/${backend}/root/sign-intermediate`;
},
});

View File

@ -1,73 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { parsePkiCert } from 'vault/utils/parse-pki-cert';
import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
namespace: 'v1',
url(snapshot, action) {
const { backend, caType, type } = snapshot.attributes();
if (action === 'sign-intermediate') {
return `/v1/${backend}/root/sign-intermediate`;
}
if (action === 'set-signed-intermediate') {
return `/v1/${backend}/intermediate/set-signed`;
}
if (action === 'upload') {
return `/v1/${backend}/config/ca`;
}
return `/v1/${backend}/${caType}/generate/${type}`;
},
createRecordOrUpdate(store, type, snapshot, requestType) {
const serializer = store.serializerFor('application');
const isUpload = snapshot.attr('uploadPemBundle');
const isSetSignedIntermediate = snapshot.adapterOptions.method === 'setSignedIntermediate';
let action = snapshot.adapterOptions.method === 'signIntermediate' ? 'sign-intermediate' : null;
let data;
if (isUpload) {
action = 'upload';
data = { pem_bundle: snapshot.attr('pemBundle') };
} else if (isSetSignedIntermediate) {
action = 'set-signed-intermediate';
data = { certificate: snapshot.attr('certificate') };
} else {
data = serializer.serialize(snapshot, requestType);
// The type parameter is serialized but is part of the URL. This means
// we'll get an unknown parameter warning back from the server if we
// send it. Remove it instead.
delete data.type;
}
return this.ajax(this.url(snapshot, action), 'POST', { data }).then((response) => {
// uploading CA, setting signed intermediate cert, and attempting to generate
// a new CA if one exists, all return a 204
if (!response) {
response = {};
}
response.id = snapshot.id;
response.modelName = type.modelName;
// only parse if certificate is attached to response
if (response.data && response.data.certificate) {
const caCertMetadata = parsePkiCert(response.data);
const transformedResponse = { ...response, ...caCertMetadata };
store.pushPayload(type.modelName, transformedResponse);
} else {
store.pushPayload(type.modelName, response);
}
});
},
createRecord() {
return this.createRecordOrUpdate(...arguments);
},
updateRecord() {
return this.createRecordOrUpdate(...arguments);
},
});

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Adapter from './pki';
export default Adapter.extend({
url(role, snapshot) {
if (snapshot.attr('signVerbatim') === true) {
return `/v1/${role.backend}/sign-verbatim/${role.name}`;
}
return `/v1/${role.backend}/sign/${role.name}`;
},
pathForType() {
return 'sign';
},
});

View File

@ -1,27 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { assert } from '@ember/debug';
import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
namespace: 'v1',
url(/*role*/) {
assert('Override the `url` method to extend the PKI adapter', false);
},
createRecord(store, type, snapshot, requestType) {
const serializer = store.serializerFor(type.modelName);
const data = serializer.serialize(snapshot, requestType);
const role = snapshot.attr('role');
return this.ajax(this.url(role, snapshot), 'POST', { data }).then((response) => {
response.id = snapshot.id;
response.modelName = type.modelName;
store.pushPayload(type.modelName, response);
});
},
});

View File

@ -1,133 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import AdapterError from '@ember-data/adapter/error';
import { hash, resolve } from 'rsvp';
import { capitalize } from '@ember/string';
import { set } from '@ember/object';
import ApplicationAdapter from '../application';
export default ApplicationAdapter.extend({
namespace: 'v1',
urlFor(backend, section) {
const urls = {
tidy: `/v1/${backend}/tidy`,
urls: `/v1/${backend}/config/urls`,
crl: `/v1/${backend}/config/crl`,
};
return urls[section];
},
createOrUpdate(store, type, snapshot) {
const url = this.urlFor(snapshot.record.get('backend'), snapshot.adapterOptions.method);
const serializer = store.serializerFor(type.modelName);
if (!url) {
return;
}
const data = snapshot.adapterOptions.fields.reduce((data, field) => {
const attr = snapshot.attr(field);
if (attr) {
serializer.serializeAttribute(snapshot, data, field, attr);
} else {
data[serializer.keyForAttribute(field)] = attr;
}
return data;
}, {});
return this.ajax(url, 'POST', { data }).then((resp) => {
const response = resp || {};
response.id = `${snapshot.record.get('backend')}-${snapshot.adapterOptions.method}`;
return response;
});
},
createRecord() {
return this.createOrUpdate(...arguments);
},
updateRecord() {
return this.createOrUpdate(...arguments, 'update');
},
fetchSection(backendPath, section) {
const sections = ['cert', 'urls', 'crl', 'tidy'];
if (!section || !sections.includes(section)) {
const error = new AdapterError();
set(error, 'httpStatus', 404);
throw error;
}
return this[`fetch${capitalize(section)}`](backendPath);
},
id(backendPath) {
return backendPath + '-config-ca';
},
fetchCert(backendPath) {
// these are all un-authed so using `fetch` directly works
const derURL = `/v1/${backendPath}/ca`;
const pemURL = `${derURL}/pem`;
const chainURL = `${derURL}_chain`;
return hash({
backend: backendPath,
id: this.id(backendPath),
der: this.rawRequest(derURL, 'GET', { unauthenticated: true }).then((response) => response.blob()),
pem: this.rawRequest(pemURL, 'GET', { unauthenticated: true }).then((response) => response.text()),
ca_chain: this.rawRequest(chainURL, 'GET', { unauthenticated: true }).then((response) =>
response.text()
),
});
},
fetchUrls(backendPath) {
const url = `/v1/${backendPath}/config/urls`;
const id = this.id(backendPath);
return this.ajax(url, 'GET')
.then((resp) => {
resp.id = id;
resp.backend = backendPath;
return resp;
})
.catch((e) => {
if (e.httpStatus === 404) {
return resolve({ id });
} else {
throw e;
}
});
},
fetchCrl(backendPath) {
const url = `/v1/${backendPath}/config/crl`;
const id = this.id(backendPath);
return this.ajax(url, 'GET')
.then((resp) => {
resp.id = id;
resp.backend = backendPath;
return resp;
})
.catch((e) => {
if (e.httpStatus === 404) {
return { id };
} else {
throw e;
}
});
},
fetchTidy(backendPath) {
const id = this.id(backendPath);
return resolve({ id, backend: backendPath });
},
queryRecord(store, type, query) {
const { backend, section } = query;
return this.fetchSection(backend, section).then((resp) => {
resp.backend = backend;
return resp;
});
},
});

View File

@ -1,75 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { assign } from '@ember/polyfills';
import ApplicationAdapter from '../application';
import { encodePath } from 'vault/utils/path-encoding-helpers';
export default ApplicationAdapter.extend({
namespace: 'v1',
createOrUpdate(store, type, snapshot, requestType) {
const { name, backend } = snapshot.record;
const serializer = store.serializerFor(type.modelName);
const data = serializer.serialize(snapshot, requestType);
const url = this.urlForRole(backend, name);
return this.ajax(url, 'POST', { data });
},
createRecord() {
return this.createOrUpdate(...arguments);
},
updateRecord() {
return this.createOrUpdate(...arguments, 'update');
},
deleteRecord(store, type, snapshot) {
const { id } = snapshot;
return this.ajax(this.urlForRole(snapshot.record.get('backend'), id), 'DELETE');
},
pathForType() {
return 'roles';
},
urlForRole(backend, id) {
let url = `${this.buildURL()}/${encodePath(backend)}/roles`;
if (id) {
url = url + '/' + encodePath(id);
}
return url;
},
optionsForQuery(id) {
const data = {};
if (!id) {
data['list'] = true;
}
return { data };
},
fetchByQuery(store, query) {
const { id, backend } = query;
return this.ajax(this.urlForRole(backend, id), 'GET', this.optionsForQuery(id)).then((resp) => {
const data = {
id,
name: id,
backend,
};
return assign({}, resp, data);
});
},
query(store, type, query) {
return this.fetchByQuery(store, query);
},
queryRecord(store, type, query) {
return this.fetchByQuery(store, query);
},
});

View File

@ -68,7 +68,6 @@ export default class App extends Application {
externalRoutes: {
secrets: 'vault.cluster.secrets.backends',
externalMountIssuer: 'vault.cluster.secrets.backend.pki.issuers.issuer.details',
secretsListRoot: 'vault.cluster.secrets.backend.list-root',
secretsListRootConfiguration: 'vault.cluster.secrets.backend.configuration',
},
},

View File

@ -1,190 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { inject as service } from '@ember/service';
import { not } from '@ember/object/computed';
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
classNames: 'config-pki-ca',
store: service('store'),
flashMessages: service(),
errors: null,
/*
* @param boolean
* @private
* bool that gets flipped if you have a CA cert and click the Replace Cert button
*/
replaceCA: false,
/*
* @param boolean
* @private
* bool that gets flipped if you push the click the "Set signed intermediate" button
*/
setSignedIntermediate: false,
/*
* @param boolean
* @private
* bool that gets flipped if you push the click the "Set signed intermediate" button
*/
signIntermediate: false,
/*
* @param boolean
* @private
*
* true when there's no CA cert currently configured
*/
needsConfig: not('config.pem'),
/*
* @param DS.Model
* @private
*
* a `pki-ca-certificate` model used to back the form when uploading or creating a CA cert
* created and set on `init`, and unloaded on willDestroy
*
*/
model: null,
/*
* @param DS.Model
* @public
*
* a `pki-config` model - passed in in the component usage
*
*/
config: null,
/*
* @param Function
* @public
*
* function that gets called to refresh the config model
*
*/
onRefresh() {},
loading: false,
willDestroy() {
const ca = this.model;
if (ca && !ca.isDestroyed && !ca.isDestroying) {
ca.unloadRecord();
}
this._super(...arguments);
},
createOrReplaceModel(modelType) {
const ca = this.model;
const config = this.config;
const store = this.store;
const backend = config.get('backend');
if (ca) {
ca.unloadRecord();
}
const caCert = store.createRecord(modelType || 'pki-ca-certificate', {
id: `${backend}-ca-cert`,
backend,
});
this.set('model', caCert);
},
/*
* @private
* @returns array
*
* When a CA is configured, we let them download
* the CA in der, pem, and the CA Chain in pem (if one exists)
*
* This array provides the text and download hrefs for those links.
*
*/
downloadHrefs: computed('config', 'config.{backend,pem,caChain,der}', function () {
const config = this.config;
const { backend, pem, caChain, der } = config;
if (!pem) {
return [];
}
const pemFile = new Blob([pem], { type: 'text/plain' });
const links = [
{
display: 'Download CA Certificate in PEM format',
name: `${backend}_ca.pem`,
url: URL.createObjectURL(pemFile),
},
{
display: 'Download CA Certificate in DER format',
name: `${backend}_ca.der`,
url: URL.createObjectURL(der),
},
];
if (caChain) {
const caChainFile = new Blob([caChain], { type: 'text/plain' });
links.push({
display: 'Download CA Certificate Chain',
name: `${backend}_ca_chain.pem`,
url: URL.createObjectURL(caChainFile),
});
}
return links;
}),
actions: {
saveCA(method) {
this.set('loading', true);
const model = this.model;
const isUpload = this.model.uploadPemBundle;
model
.save({ adapterOptions: { method } })
.then(() => {
if (method === 'setSignedIntermediate' || isUpload) {
this.send('refresh');
this.flashMessages.success('The certificate for this backend has been updated.');
}
})
.catch((e) => {
this.set('errors', e.errors);
})
.finally(() => {
this.set('loading', false);
});
},
refresh() {
this.setProperties({
setSignedIntermediate: false,
signIntermediate: false,
replaceCA: false,
});
this.onRefresh();
},
toggleReplaceCA() {
if (!this.replaceCA) {
this.createOrReplaceModel();
}
this.toggleProperty('replaceCA');
},
toggleVal(name, val) {
if (!name) {
return;
}
const model = name === 'signIntermediate' ? 'pki-ca-certificate-sign' : null;
if (!this.get(name)) {
this.createOrReplaceModel(model);
}
if (val !== undefined) {
this.set(name, val);
} else {
this.toggleProperty(name);
}
},
},
});

View File

@ -1,78 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { get } from '@ember/object';
export default Component.extend({
classNames: 'config-pki',
flashMessages: service(),
errors: null,
/*
*
* @param String
* @public
* String corresponding to the route parameter for the current section
*
*/
section: null,
/*
* @param DS.Model
* @public
*
* a `pki-config` model - passed in in the component usage
*
*/
config: null,
/*
* @param Function
* @public
*
* function that gets called to refresh the config model
*
*/
onRefresh() {},
loading: false,
actions: {
handleCrlTtl({ enabled, goSafeTimeString }) {
this.config.disable = !enabled; // when TTL enabled, config disable=false
this.config.expiry = goSafeTimeString;
},
save(section) {
this.set('loading', true);
const config = this.config;
config
.save({
adapterOptions: {
method: section,
fields: get(config, `${section}Attrs`).map((attr) => attr.name),
},
})
.then(() => {
this.flashMessages.success(`The ${section} config for this backend has been updated.`);
// attrs aren't persistent for Tidy
if (section === 'tidy') {
config.rollbackAttributes();
}
this.send('refresh');
})
.catch((e) => {
this.set('errors', e.errors);
})
.finally(() => {
this.set('loading', false);
});
},
refresh() {
this.onRefresh();
},
},
});

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
/**
* @module PkiCertPopup
* PkiCertPopup component is the hotdog menu button that allows you to see details or revoke a certificate.
*
* @example
* ```js
* <PkiCertPopup @item={{@item}}/>
* ```
* @param {class} item - the PKI cert in question.
*/
export default class PkiCertPopup extends Component {
get item() {
return this.args.item || null;
}
@action
delete(item) {
item.save({ adapterOptions: { method: 'revoke' } });
}
}

View File

@ -1,14 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import RoleEdit from '../role-edit';
export default RoleEdit.extend({
actions: {
delete() {
this.model.save({ adapterOptions: { method: 'revoke' } });
},
},
});

View File

@ -1,13 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import RoleEdit from '../role-edit';
export default RoleEdit.extend({
init() {
this._super(...arguments);
this.set('backendType', 'pki');
},
});

View File

@ -87,7 +87,7 @@ const MOUNTABLE_SECRET_ENGINES = [
{
displayName: 'PKI Certificates',
type: 'pki',
// engineRoute: 'pki.overview', // TODO VAULT-13822
engineRoute: 'pki.overview',
category: 'generic',
},
{

View File

@ -1,80 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { attr } from '@ember-data/model';
import { copy } from 'ember-copy';
import { computed } from '@ember/object';
import Certificate from './pki-certificate-sign';
export default Certificate.extend({
backend: attr('string', {
readOnly: true,
}),
useCsrValues: attr('boolean', {
defaultValue: false,
label: 'Use CSR values',
}),
maxPathLength: attr('number', {
defaultValue: -1,
}),
permittedDnsDomains: attr('string', {
label: 'Permitted DNS domains',
}),
ou: attr({
label: 'OU (OrganizationalUnit)',
editType: 'stringArray',
}),
organization: attr({
editType: 'stringArray',
}),
country: attr({
editType: 'stringArray',
}),
locality: attr({
editType: 'stringArray',
label: 'Locality/City',
}),
province: attr({
editType: 'stringArray',
label: 'Province/State',
}),
streetAddress: attr({
editType: 'stringArray',
}),
postalCode: attr({
editType: 'stringArray',
}),
fieldGroups: computed('useCsrValues', function () {
const options = [
{
Options: [
'altNames',
'ipSans',
'ttl',
'excludeCnFromSans',
'maxPathLength',
'permittedDnsDomains',
'ou',
'organization',
'otherSans',
],
},
{
'Address Options': ['country', 'locality', 'province', 'streetAddress', 'postalCode'],
},
];
let groups = [
{
default: ['csr', 'commonName', 'format', 'useCsrValues'],
},
];
if (this.useCsrValues === false) {
groups = groups.concat(options);
}
return this.fieldsToAttrs(copy(groups, true));
}),
});

View File

@ -1,157 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import Certificate from './pki/cert';
export default Certificate.extend({
DISPLAY_FIELDS: computed(function () {
return [
'csr',
'certificate',
'commonName',
'issueDate',
'expiryDate',
'issuingCa',
'caChain',
'privateKey',
'privateKeyType',
'serialNumber',
];
}),
addBasicConstraints: attr('boolean', {
label: 'Add a Basic Constraints extension with CA: true',
helpText:
'Only needed as a workaround in some compatibility scenarios with Active Directory Certificate Services',
}),
backend: attr('string', {
readOnly: true,
}),
canParse: attr('boolean'),
caType: attr('string', {
possibleValues: ['root', 'intermediate'],
defaultValue: 'root',
label: 'CA Type',
readOnly: true,
}),
commonName: attr('string'),
csr: attr('string', {
editType: 'textarea',
label: 'CSR',
masked: true,
}),
expiryDate: attr('string', {
label: 'Expiration date',
}),
issueDate: attr('string'),
keyBits: attr('number', {
defaultValue: 2048,
}),
keyType: attr('string', {
possibleValues: ['rsa', 'ec', 'ed25519'],
defaultValue: 'rsa',
}),
maxPathLength: attr('number', {
defaultValue: -1,
}),
organization: attr({
editType: 'stringArray',
}),
ou: attr({
label: 'OU (OrganizationalUnit)',
editType: 'stringArray',
}),
pemBundle: attr('string', {
label: 'PEM bundle',
editType: 'file',
}),
permittedDnsDomains: attr('string', {
label: 'Permitted DNS domains',
}),
privateKeyFormat: attr('string', {
possibleValues: ['', 'der', 'pem', 'pkcs8'],
defaultValue: '',
}),
type: attr('string', {
possibleValues: ['internal', 'exported'],
defaultValue: 'internal',
}),
uploadPemBundle: attr('boolean', {
label: 'Upload PEM bundle',
readOnly: true,
}),
// address attrs
country: attr({
editType: 'stringArray',
}),
locality: attr({
editType: 'stringArray',
label: 'Locality/City',
}),
streetAddress: attr({
editType: 'stringArray',
}),
postalCode: attr({
editType: 'stringArray',
}),
province: attr({
editType: 'stringArray',
label: 'Province/State',
}),
fieldDefinition: computed('caType', 'uploadPemBundle', function () {
const type = this.caType;
const isUpload = this.uploadPemBundle;
const groups = [{ default: ['caType', 'uploadPemBundle'] }];
if (isUpload) {
groups[0].default.push('pemBundle');
} else {
groups[0].default.push('type', 'commonName');
if (type === 'root') {
groups.push({
Options: [
'altNames',
'ipSans',
'ttl',
'format',
'privateKeyFormat',
'keyType',
'keyBits',
'maxPathLength',
'permittedDnsDomains',
'excludeCnFromSans',
'ou',
'organization',
'otherSans',
],
});
}
if (type === 'intermediate') {
groups.push({
Options: [
'altNames',
'ipSans',
'format',
'privateKeyFormat',
'keyType',
'keyBits',
'excludeCnFromSans',
'addBasicConstraints',
'ou',
'organization',
'otherSans',
],
});
}
}
groups.push({
'Address Options': ['country', 'locality', 'province', 'streetAddress', 'postalCode'],
});
return groups;
}),
});

View File

@ -1,39 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { attr } from '@ember-data/model';
import { copy } from 'ember-copy';
import { computed } from '@ember/object';
import Certificate from './pki/cert';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default Certificate.extend({
signVerbatim: attr('boolean', {
readOnly: true,
defaultValue: false,
}),
useOpenAPI: true,
csr: attr('string', {
label: 'Certificate Signing Request (CSR)',
editType: 'textarea',
}),
fieldGroups: computed('newFields', 'signVerbatim', function () {
const options = { Options: ['altNames', 'ipSans', 'ttl', 'excludeCnFromSans', 'otherSans'] };
let groups = [
{
default: ['csr', 'commonName', 'format', 'signVerbatim'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
if (this.signVerbatim === false) {
groups.push(options);
}
return this.fieldsToAttrs(copy(groups, true));
}),
});

View File

@ -1,30 +0,0 @@
# PKI filenames
> delete this file when PKI redesign is complete
Now that all things PKI live in a `/pki` folder we are removing `pki` from the filename in any newly created files for the pki redesign (ex. 'role.js' instead of 'pki-role.js'). Aside from `cert.js` all of the old pki files are prepended with `pki-`
Old files:
├── models/
│   ├── pki/
│   │   ├── cert.js
│   │   ├── pki-config.js
│   │   ├── pki-role.js
│   ├── pki-ca-certificate-sign.js
│   ├── pki-ca-certificate.js
│   ├── pki-certificate-sign.js
├── serializers/
│   ├── pki/
│   │   ├── cert.js
│   │   ├── pki-config.js
│   │   ├── pki-role.js
├── adapters/
│   ├── pki/
│   │   ├── cert.js
│   │   ├── pki-config.js
│   │   ├── pki-role.js
│   ├── pki-ca-certificate-sign.js
│   ├── pki-ca-certificate.js
│   ├── pki-certificate-sign.js
│   ├── pki.js

View File

@ -1,139 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Model, { attr } from '@ember-data/model';
import { alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
export default Model.extend({
idPrefix: 'cert/',
//the id prefixed with `cert/` so we can use it as the *secret param for the secret show route
idForNav: attr('string', {
readOnly: true,
}),
DISPLAY_FIELDS: computed(function () {
return [
'certificate',
'commonName',
'issuingCa',
'caChain',
'privateKey',
'privateKeyType',
'revocationTime',
'issueDate',
'expiryDate',
'serialNumber',
];
}),
altNames: attr('string', {
label: 'DNS/Email Subject Alternative Names (SANs)',
}),
backend: attr('string', {
readOnly: true,
}),
caChain: attr('string', {
label: 'CA chain',
masked: true,
}),
canParse: attr('boolean'),
certificate: attr('string', {
masked: true,
}),
commonName: attr('string'),
excludeCnFromSans: attr('boolean', {
label: 'Exclude Common Name from Subject Alternative Names (SANs)',
defaultValue: false,
}),
expiryDate: attr('string', {
label: 'Expiration date',
}),
format: attr('string', {
defaultValue: 'pem',
possibleValues: ['pem', 'der', 'pem_bundle'],
}),
ipSans: attr('string', {
label: 'IP Subject Alternative Names (SANs)',
}),
issueDate: attr('string'),
issuingCa: attr('string', {
label: 'Issuing CA',
masked: true,
}),
otherSans: attr({
editType: 'stringArray',
label: 'Other SANs',
helpText:
'The format is the same as OpenSSL: <oid>;<type>:<value> where the only current valid type is UTF8',
}),
privateKey: attr('string', {
masked: true,
}),
privateKeyType: attr('string'),
revocationTime: attr('number'),
role: attr('object', {
readOnly: true,
}),
serialNumber: attr('string'),
ttl: attr({
label: 'TTL',
editType: 'ttl',
}),
fieldsToAttrs(fieldGroups) {
return fieldToAttrs(this, fieldGroups);
},
fieldDefinition: computed(function () {
const groups = [
{ default: ['commonName', 'format'] },
{ Options: ['altNames', 'ipSans', 'ttl', 'excludeCnFromSans', 'otherSans'] },
];
return groups;
}),
fieldGroups: computed('fieldDefinition', function () {
return this.fieldsToAttrs(this.fieldDefinition);
}),
attrs: computed('DISPLAY_FIELDS', 'certificate', 'csr', function () {
const keys = this.certificate || this.csr ? this.DISPLAY_FIELDS.slice(0) : [];
return expandAttributeMeta(this, keys);
}),
toCreds: computed(
'certificate',
'issuingCa',
'caChain',
'privateKey',
'privateKeyType',
'revocationTime',
'serialNumber',
function () {
const props = {
certificate: this.certificate,
issuingCa: this.issuingCa,
caChain: this.caChain,
privateKey: this.privateKey,
privateKeyType: this.privateKeyType,
revocationTime: this.revocationTime,
serialNumber: this.serialNumber,
};
const propsWithVals = Object.keys(props).reduce((ret, prop) => {
if (props[prop]) {
ret[prop] = props[prop];
return ret;
}
return ret;
}, {});
return JSON.stringify(propsWithVals, null, 2);
}
),
revokePath: lazyCapabilities(apiPath`${'backend'}/revoke`, 'backend'),
canRevoke: alias('revokePath.canUpdate'),
});

View File

@ -1,61 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Model, { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
export default Model.extend({
backend: attr('string'),
der: attr(),
pem: attr('string'),
caChain: attr('string'),
attrList(keys) {
return expandAttributeMeta(this, keys);
},
//urls
urlsAttrs: computed(function () {
const keys = ['issuingCertificates', 'crlDistributionPoints', 'ocspServers'];
return this.attrList(keys);
}),
issuingCertificates: attr({
editType: 'stringArray',
}),
crlDistributionPoints: attr({
label: 'CRL Distribution Points',
editType: 'stringArray',
}),
ocspServers: attr({
label: 'OCSP Servers',
editType: 'stringArray',
}),
//tidy
tidyAttrs: computed(function () {
const keys = ['tidyCertStore', 'tidyRevocationList', 'safetyBuffer'];
return this.attrList(keys);
}),
tidyCertStore: attr('boolean', {
defaultValue: false,
label: 'Tidy the Certificate Store',
}),
tidyRevocationList: attr('boolean', {
defaultValue: false,
label: 'Tidy the Revocation List (CRL)',
}),
safetyBuffer: attr({
defaultValue: '72h',
editType: 'ttl',
}),
crlAttrs: computed(function () {
const keys = ['expiry', 'disable'];
return this.attrList(keys);
}),
//crl
expiry: attr('string', { defaultValue: '72h' }),
disable: attr('boolean', { defaultValue: false }),
});

View File

@ -1,92 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Model, { attr } from '@ember-data/model';
import { alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default Model.extend({
backend: attr('string', {
readOnly: true,
}),
name: attr('string', {
label: 'Role name',
fieldValue: 'name',
readOnly: true,
}),
useOpenAPI: true,
getHelpUrl: function (backend) {
return `/v1/${backend}/roles/example?help=1`;
},
updatePath: lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id'),
canDelete: alias('updatePath.canDelete'),
canEdit: alias('updatePath.canUpdate'),
canRead: alias('updatePath.canRead'),
generatePath: lazyCapabilities(apiPath`${'backend'}/issue/${'id'}`, 'backend', 'id'),
canGenerate: alias('generatePath.canUpdate'),
signPath: lazyCapabilities(apiPath`${'backend'}/sign/${'id'}`, 'backend', 'id'),
canSign: alias('signPath.canUpdate'),
signVerbatimPath: lazyCapabilities(apiPath`${'backend'}/sign-verbatim/${'id'}`, 'backend', 'id'),
canSignVerbatim: alias('signVerbatimPath.canUpdate'),
fieldGroups: computed('newFields', function () {
let groups = [
{ default: ['name', 'keyType'] },
{
Options: [
'keyBits',
'ttl',
'maxTtl',
'allowAnyName',
'enforceHostnames',
'allowIpSans',
'requireCn',
'useCsrCommonName',
'useCsrSans',
'ou',
'organization',
'keyUsage',
'allowedOtherSans',
'notBeforeDuration',
],
},
{
'Address Options': ['country', 'locality', 'province', 'streetAddress', 'postalCode'],
},
{
'Domain Handling': [
'allowLocalhost',
'allowBareDomains',
'allowSubdomains',
'allowGlobDomains',
'allowedDomains',
],
},
{
'Extended Key Usage': [
'serverFlag',
'clientFlag',
'codeSigningFlag',
'emailProtectionFlag',
'extKeyUsageOids',
],
},
{
Advanced: ['generateLease', 'noStore', 'basicConstraintsValidForNonCa', 'policyIdentifiers'],
},
];
const excludedFields = ['extKeyUsage'];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, excludedFields);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -7,7 +7,7 @@ import AdapterError from '@ember-data/adapter/error';
import { set } from '@ember/object';
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
const CONFIGURABLE_BACKEND_TYPES = ['aws', 'ssh', 'pki'];
const CONFIGURABLE_BACKEND_TYPES = ['aws', 'ssh'];
export default Route.extend({
store: service(),
@ -32,15 +32,9 @@ export default Route.extend({
});
},
afterModel(model, transition) {
afterModel(model) {
const type = model.get('type');
if (type === 'pki') {
if (transition.targetName === this.routeName) {
return this.transitionTo(`${this.routeName}.section`, 'cert');
} else {
return;
}
}
if (type === 'aws') {
return this.store
.queryRecord('secret-engine', {

View File

@ -1,15 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Route from '@ember/routing/route';
export default Route.extend({
beforeModel(transition) {
const type = this.modelFor('vault.cluster.settings.configure-secret-backend').get('type');
if (type === 'pki' && transition.targetName === this.routeName) {
return this.transitionTo('vault.cluster.settings.configure-secret-backend.section', 'cert');
}
},
});

View File

@ -1,59 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import AdapterError from '@ember-data/adapter/error';
import { set } from '@ember/object';
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
// ARG TODO glimmerize
const SECTIONS_FOR_TYPE = {
pki: ['cert', 'urls', 'crl', 'tidy'],
};
export default Route.extend({
store: service(),
fetchModel() {
const { section_name: sectionName } = this.paramsFor(this.routeName);
const backendModel = this.modelFor('vault.cluster.settings.configure-secret-backend');
const type = backendModel.get('type');
let modelType;
if (type === 'pki') {
// pki models are in models/pki
modelType = `${type}/${type}-config`;
} else {
modelType = `${type}-config`;
}
return this.store
.queryRecord(modelType, {
backend: backendModel.id,
section: sectionName,
})
.then((model) => {
model.set('backendType', type);
model.set('section', sectionName);
return model;
});
},
model(params) {
const { section_name: sectionName } = params;
const backendModel = this.modelFor('vault.cluster.settings.configure-secret-backend');
const sections = SECTIONS_FOR_TYPE[backendModel.get('type')];
const hasSection = sections.includes(sectionName);
if (!backendModel || !hasSection) {
const error = new AdapterError();
set(error, 'httpStatus', 404);
throw error;
}
return this.fetchModel();
},
setupController(controller) {
this._super(...arguments);
controller.set('onRefresh', () => this.fetchModel());
},
});

View File

@ -1,77 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import RESTSerializer from '@ember-data/serializer/rest';
import { isNone, isBlank } from '@ember/utils';
import { assign } from '@ember/polyfills';
import { decamelize } from '@ember/string';
import { parsePkiCert } from 'vault/utils/parse-pki-cert';
export default RESTSerializer.extend({
keyForAttribute: function (attr) {
return decamelize(attr);
},
pushPayload(store, payload) {
const transformedPayload = this.normalizeResponse(
store,
store.modelFor(payload.modelName),
payload,
payload.id,
'findRecord'
);
return store.push(transformedPayload);
},
normalizeItems(payload) {
if (payload.data && payload.data.keys && Array.isArray(payload.data.keys)) {
const ret = payload.data.keys.map((key) => {
const model = {
id_for_nav: `cert/${key}`,
id: key,
};
if (payload.backend) {
model.backend = payload.backend;
}
return model;
});
return ret;
}
assign(payload, payload.data);
delete payload.data;
return payload;
},
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
const responseJSON = this.normalizeItems(payload);
const { modelName } = primaryModelClass;
let transformedPayload, certMetadata;
// hits cert/list endpoint first which returns an array of keys, only want to parse if response contains certificates
if (!Array.isArray(responseJSON)) {
certMetadata = parsePkiCert(responseJSON);
transformedPayload = { [modelName]: { ...certMetadata, ...responseJSON } };
} else {
transformedPayload = { [modelName]: responseJSON };
}
return this._super(store, primaryModelClass, transformedPayload, id, requestType);
},
serializeAttribute(snapshot, json, key, attributes) {
const val = snapshot.attr(key);
const valHasNotChanged = isNone(snapshot.changedAttributes()[key]);
const valIsBlank = isBlank(val);
if (attributes.options.readOnly) {
return;
}
if (attributes.type === 'object' && val && Object.keys(val).length > 0 && valHasNotChanged) {
return;
}
if (valIsBlank && valHasNotChanged) {
return;
}
this._super(snapshot, json, key, attributes);
},
});

View File

@ -1,34 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import RESTSerializer from '@ember-data/serializer/rest';
import { assign } from '@ember/polyfills';
import { decamelize } from '@ember/string';
export default RESTSerializer.extend({
keyForAttribute: function (attr) {
return decamelize(attr);
},
normalizeAll(payload) {
if (payload.data) {
const data = assign({}, payload, payload.data);
return [data];
}
return [payload];
},
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
const records = this.normalizeAll(payload);
const { modelName } = primaryModelClass;
let transformedPayload = { [modelName]: records };
// just return the single object because ember is picky
if (requestType === 'queryRecord') {
transformedPayload = { [modelName]: records[0] };
}
return this._super(store, primaryModelClass, transformedPayload, id, requestType);
},
});

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import RoleSerializer from '../role';
export default RoleSerializer.extend();

View File

@ -24,11 +24,6 @@
width: 12px;
}
&.is-success {
background-color: $green-050;
color: $green-700;
}
&.has-extra-padding {
padding: $size-11 $spacing-xxs;
}

View File

@ -1,46 +0,0 @@
<NamespaceReminder @mode="save" @noun="PKI change" />
{{#if (eq this.section "tidy")}}
<p class="has-text-grey-dark" data-test-text="true">
You can tidy up the backend storage and/or CRL by removing certificates that have expired and are past a certain buffer
period beyond their expiration time.
</p>
{{else if (eq this.section "crl")}}
<h2 class="title is-5" data-test-title="true">
Certificate Revocation List (CRL) config
</h2>
<p class="has-text-grey-dark" data-test-text="true">
Set the duration for which the generated CRL should be marked valid.
</p>
{{/if}}
<MessageError @model={{this.config}} @errors={{this.errors}} />
<form {{action "save" this.section on="submit"}} class="box is-shadowless is-marginless is-fullwidth has-slim-padding">
{{#if (eq this.section "crl")}}
<TtlPicker
data-test-input="expiry"
@onChange={{action "handleCrlTtl"}}
@label={{if (get this.config "disable") "CRL building disabled" "CRL building enabled"}}
@helperTextDisabled="The CRL will not be built."
@helperTextEnabled="The CRL will expire after"
@initialValue={{get this.config "expiry"}}
@initialEnabled={{not (get this.config "disable")}}
/>
{{else}}
{{#each (get this.config (concat this.section "Attrs")) as |attr|}}
<FormField @attr={{attr}} @model={{this.config}} @data-test-field={{attr.name}} />
{{/each}}
{{/if}}
<div class="field has-top-margin-m 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>
</form>

View File

@ -1,19 +0,0 @@
<div class="tabs-container box is-bottomless is-fullwidth is-paddingless">
<nav class="tabs">
<ul>
{{#each (array "cert" "urls" "crl" "tidy") as |section|}}
<LinkTo @route="vault.cluster.settings.configure-secret-backend.section" @model={{section}}>
{{#if (eq section "cert")}}
CA certificate
{{else if (eq section "urls")}}
URLs
{{else if (eq section "crl")}}
CRL
{{else if (eq section "tidy")}}
Tidy
{{/if}}
</LinkTo>
{{/each}}
</ul>
</nav>
</div>

View File

@ -1,25 +0,0 @@
<PopupMenu @name="role-aws-nav" @contentClass="is-wide">
<nav class="menu">
<ul class="menu-list">
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.idForNav}} data-test-pki-cert-link="show">
Details
</LinkTo>
</li>
{{#if @item.canRevoke}}
<li class="action">
<ConfirmAction
@buttonClasses="link is-destroy"
@onConfirmAction={{fn this.delete @item}}
@confirmTitle="Revoke this cert?"
@confirmMessage="Any services using this cert may be affected."
@confirmButtonText="Revoke"
data-test-cert-revoke-delete={{@item.id}}
>
Revoke
</ConfirmAction>
</li>
{{/if}}
</ul>
</nav>
</PopupMenu>

View File

@ -1,94 +0,0 @@
<PageHeader as |p|>
<p.top>
<KeyValueHeader
@baseKey={{hash display=this.model.id id=this.model.idForNav}}
@path="vault.cluster.secrets.backend.list"
@mode={{this.mode}}
@root={{this.root}}
@showCurrent={{true}}
/>
</p.top>
<p.levelLeft>
<h1 class="title is-3" data-test-secret-header="true">
PKI Certificate
</h1>
</p.levelLeft>
</PageHeader>
{{#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 common name or the issue and expiration dates. This will not interfere with the certificate's functionality."
data-test-warning
/>
{{/if}}
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
<MessageError @model={{this.model}} />
{{#each this.model.attrs as |attr|}}
{{#if (eq attr.type "object")}}
<InfoTableRow
data-test-table-row
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{stringify (get this.model attr.name)}}
/>
{{else}}
{{#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
@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 if (eq attr.name "revocationTime")}}
<InfoTableRow
data-test-table-row
@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"}}
/>
{{else}}
<InfoTableRow
data-test-table-row
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get this.model attr.name}}
/>
{{/if}}
{{/if}}
{{/each}}
</div>
<div class="field is-grouped is-grouped-split box is-fullwidth is-bottomless">
<div class="field is-grouped">
<div class="control">
<CopyButton
@clipboardText={{this.model.toCreds}}
@class="button is-primary"
@buttonType="button"
@success={{action (set-flash-message "Credentials copied!")}}
>
Copy credentials
</CopyButton>
</div>
<div class="control">
<LinkTo @route="vault.cluster.secrets.backend.list-root" @query={{hash tab="cert"}} class="button">
Back
</LinkTo>
</div>
</div>
{{#if (and (not this.model.revocationTime) this.model.canRevoke)}}
<ConfirmAction
@buttonClasses="button"
@onConfirmAction={{action "delete"}}
@confirmTitle="Revoke this cert?"
@confirmMessage="Any services using this cert may be affected."
@confirmButtonText="Revoke"
>
Revoke
</ConfirmAction>
{{/if}}
</div>

View File

@ -1,113 +0,0 @@
<PageHeader as |p|>
<p.top>
<KeyValueHeader
@baseKey={{this.model}}
@path="vault.cluster.secrets.backend.list"
@mode={{this.mode}}
@root={{this.root}}
@showCurrent={{true}}
/>
</p.top>
<p.levelLeft>
<h1 class="title is-3" data-test-secret-header="true">
{{#if (eq this.mode "create")}}
Create a PKI Role
{{else if (eq this.mode "edit")}}
Edit PKI Role
{{else}}
PKI Role
<code>{{this.model.id}}</code>
{{/if}}
</h1>
</p.levelLeft>
</PageHeader>
{{#if (eq this.mode "show")}}
<Toolbar>
<ToolbarActions>
{{#if this.model.canDelete}}
<ConfirmAction @buttonClasses="toolbar-link" @onConfirmAction={{action "delete"}} data-test-role-delete="true">
Delete role
</ConfirmAction>
<div class="toolbar-separator"></div>
{{/if}}
{{#if this.model.canGenerate}}
<ToolbarSecretLink
@secret={{this.model.id}}
@mode="credentials"
@queryParams={{hash action="issue"}}
data-test-credentials-link={{true}}
>
Generate Certificate
</ToolbarSecretLink>
{{/if}}
{{#if this.model.canSign}}
<ToolbarSecretLink
@secret={{this.model.id}}
@mode="credentials"
@queryParams={{hash action="sign"}}
data-test-sign-link={{true}}
>
Sign Certificate
</ToolbarSecretLink>
{{/if}}
{{#if this.model.canEdit}}
<ToolbarSecretLink @secret={{this.model.id}} @mode="edit" data-test-edit-link={{true}} @replace={{true}}>
Edit role
</ToolbarSecretLink>
{{/if}}
</ToolbarActions>
</Toolbar>
{{/if}}
{{#if (or (eq this.mode "edit") (eq this.mode "create"))}}
<form onsubmit={{action "createOrUpdate" "create"}}>
<div class="box is-sideless is-fullwidth is-marginless">
<MessageError @model={{this.model}} />
<NamespaceReminder @mode={{this.mode}} @noun="PKI role" />
<FormFieldGroupsLoop @model={{this.model}} @mode={{this.mode}} />
</div>
<div class="field is-grouped-split box is-fullwidth is-bottomless">
<div class="control">
<button type="submit" disabled={{this.buttonDisabled}} class="button is-primary" data-test-role-create={{true}}>
{{#if (eq this.mode "create")}}
Create role
{{else if (eq this.mode "edit")}}
Save
{{/if}}
</button>
<SecretLink @mode={{if (eq this.mode "create") "list" "show"}} class="button" @secret={{this.model.id}}>
Cancel
</SecretLink>
</div>
</div>
</form>
{{else}}
<div class="box is-sideless is-fullwidth is-marginless">
{{#each this.model.fieldGroups as |fieldGroup|}}
{{#each-in fieldGroup as |group fields|}}
{{#if (or (eq group "default") (eq group "Options"))}}
{{#each fields as |attr|}}
<InfoTableRow
@alwaysRender={{true}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get this.model attr.name}}
/>
{{/each}}
{{else}}
<div class="box is-sideless is-fullwidth is-marginless">
<h2 class="title is-5">
{{group}}
</h2>
{{#each fields as |attr|}}
<InfoTableRow
@alwaysRender={{true}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get this.model attr.name}}
/>
{{/each}}
</div>
{{/if}}
{{/each-in}}
{{/each}}
</div>
{{/if}}

View File

@ -1,24 +0,0 @@
<LinkedBlock
@params={{array (concat "vault.cluster.secrets.backend." "show" (unless @item.id "-root")) @item.idForNav}}
class="list-item-row"
data-test-secret-link={{@item.id}}
@encode={{true}}
>
<div class="columns is-mobile">
<div class="column is-10">
<LinkTo
@route={{concat "vault.cluster.secrets.backend." "show" (unless @item.id "-root")}}
@model={{@item.idForNav}}
class="has-text-black has-text-weight-semibold"
>
<Icon @name="file" class="has-text-grey-light is-pulled-left" />
<div class="role-item-details">
<span class="is-underline">{{if (eq @item.id " ") "(self)" (or @item.keyWithoutParent @item.id)}}</span>
</div>
</LinkTo>
</div>
<div class="column has-text-right">
<Pki::PkiCertPopup @item={{@item}} />
</div>
</div>
</LinkedBlock>

View File

@ -1,101 +0,0 @@
<LinkedBlock
@params={{array (concat "vault.cluster.secrets.backend." "credentials" (unless @item.id "-root")) @item.backend @item.id}}
@queryParams={{hash action="issue"}}
class="list-item-row"
data-test-secret-link={{@item.id}}
@encode={{true}}
>
<div class="columns is-mobile">
<div class="column is-10">
<LinkTo
@route={{concat "vault.cluster.secrets.backend." "credentials" (unless @item.id "-root")}}
@model={{@item.id}}
@query={{hash action="issue"}}
class="has-text-black has-text-weight-semibold"
>
<Icon @name="user" class="has-text-grey-light is-pulled-left" />
<div class="role-item-details">
<span class="is-underline">{{if (eq @item.id " ") "(self)" (or @item.keyWithoutParent @item.id)}}</span>
</div>
</LinkTo>
</div>
<div class="column has-text-right">
<PopupMenu @name="role-aws-nav">
<Confirm as |c|>
<nav class="menu">
{{#if (or @item.generatePath.isPending @item.signPath.isPending)}}
<ul class="menu-list">
<li class="action">
<button disabled type="button" class="link button is-loading is-transparent">
loading
</button>
</li>
</ul>
{{else if (or @item.canGenerate @item.canSign)}}
<ul class="menu-list">
{{#if @item.canGenerate}}
<li class="action">
<LinkTo
@route="vault.cluster.secrets.backend.credentials"
@model={{@item.id}}
@query={{hash action="issue"}}
data-test-role-pki-link="generate-certificate"
>
Generate certificate
</LinkTo>
</li>
{{/if}}
{{#if @item.canSign}}
<li class="action">
<LinkTo
@route="vault.cluster.secrets.backend.credentials"
@model={{@item.id}}
@query={{hash action="sign"}}
data-test-role-pki-link="sign-certificate"
>
Sign certificate
</LinkTo>
</li>
{{/if}}
</ul>
{{/if}}
<ul class="menu-list">
{{#if @item.updatePath.isPending}}
<li class="action">
<button disabled type="button" class="link button is-loading is-transparent">
loading
</button>
</li>
<li class="action">
<button disabled type="button" class="link button is-loading is-transparent">
loading
</button>
</li>
{{else}}
{{#if @item.canRead}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.show" @model={{@item.id}} data-test-role-pki-link="show">
Details
</LinkTo>
</li>
{{/if}}
{{#if @item.canEdit}}
<li class="action">
<LinkTo @route="vault.cluster.secrets.backend.edit" @model={{@item.id}} data-test-role-pki-link="edit">
Edit
</LinkTo>
</li>
{{/if}}
{{#if @item.canDelete}}
<li class="action">
<c.Message @id={{@item.id}} @onConfirm={{@delete}} data-test-pki-role-delete={{@item.id}} />
</li>
{{/if}}
{{/if}}
</ul>
</nav>
</Confirm>
</PopupMenu>
</div>
</div>
</LinkedBlock>

View File

@ -35,8 +35,6 @@
@saveAWSRoot={{action "save" "saveAWSRoot"}}
@saveAWSLease={{action "save" "saveAWSLease"}}
/>
{{else if (eq this.model.type "pki")}}
<Pki::ConfigurePkiSecret />
{{else if (eq this.model.type "ssh")}}
<ConfigureSshSecret
@model={{this.model}}

View File

@ -1,7 +0,0 @@
{{#if (eq this.model.backendType "pki")}}
{{#if (eq this.model.section "cert")}}
<Pki::ConfigPkiCa @config={{this.model}} @onRefresh={{this.onRefresh}} />
{{else}}
<Pki::ConfigPki @config={{this.model}} @onRefresh={{this.onRefresh}} @section={{this.model.section}} />
{{/if}}
{{/if}}

View File

@ -64,8 +64,6 @@ export function parseCertificate(certificateContent) {
return {
...parsedCertificateValues,
can_parse: true,
expiry_date: expiryDate, // remove along with old PKI work
issue_date: issueDate, // remove along with old PKI work
not_valid_after: getUnixTime(expiryDate),
not_valid_before: getUnixTime(issueDate),
ttl,

View File

@ -25,93 +25,6 @@
<h1 class="title is-3">
<Icon @name={{@model.icon}} @size="24" class="has-text-grey-light" />
{{@model.id}}
{{#if this.isPki}}
{{#if @isEngine}}
<span>
<LinkToExternal
@route="secretsListRoot"
class="tag is-borderless is-underline has-text-weight-semibold has-extra-padding"
data-test-new-pki-beta-button
>
<Icon @name="arrow-left" />
Return to old PKI
</LinkToExternal>
</span>
{{else}}
{{#if this.shouldHidePkiBetaModal}}
<span>
<LinkTo
@route="vault.cluster.secrets.backend.pki.overview"
class="tag is-success is-borderless is-underline has-text-weight-semibold has-extra-padding"
>
<Icon @name="key" />
New PKI UI available
</LinkTo>
</span>
{{else}}
<button
type="button"
class="tag is-success is-v-centered text-button is-underline has-text-weight-semibold has-extra-padding"
{{on "click" (fn (mut this.modalOpen) true)}}
data-test-old-pki-beta-button
>
<Icon @name="key" />
New PKI UI available
</button>
<Modal
@title="New PKI Beta"
@onClose={{fn (mut this.modalOpen) false}}
@isActive={{this.modalOpen}}
@showCloseButton={{true}}
>
<section class="modal-card-body">
The new PKI beta includes, among other things:
<ul class="bullet">
<li>Multiple issuers can now be generated and managed in the UI.</li>
<li>Cross-signing: cross-sign multiple intermediates in a single step.</li>
<li>UI upgrades: smoother configuration, an overview page, and more certificate information.</li>
</ul>
<br />
You will always be able to return to this version.
<br />
<br />
You can also copy the following URL and bookmark it to always go to the beta version:
<br />
<code class="has-text-danger has-background-white-bis">
{{this.windowOrigin}}/ui/vault/secrets/{{@model.id}}/pki/overview
</code>
<br />
<br />
<Input
id="hide-beta-modal"
@type="checkbox"
@checked={{this.hidePkiBetaModal}}
{{on "change" this.toggleHidePkiBetaModal}}
/>
<label class="has-text-weight-semibold" for="hide-beta-modal">
Don't show me this again
</label>
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button type="button" class="button is-primary" {{on "click" this.transitionToNewPki}}>
Go to beta
</button>
<button
type="button"
class="button is-secondary"
{{on "click" (fn (mut this.modalOpen) false)}}
data-test-cancel-pki-beta-modal
>
Cancel
</button>
</footer>
</Modal>
{{/if}}
{{/if}}
{{/if}}
{{#if this.isKV}}
<span class="tag" data-test-kv-version-badge>
Version

View File

@ -3,10 +3,6 @@
* SPDX-License-Identifier: MPL-2.0
*/
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import localStorage from 'vault/lib/local-storage';
import Component from '@glimmer/component';
/**
@ -32,36 +28,7 @@ import Component from '@glimmer/component';
*/
export default class SecretListHeader extends Component {
@service router;
@tracked hidePkiBetaModal = false;
get isKV() {
return ['kv', 'generic'].includes(this.args.model.engineType);
}
get isPki() {
return this.args.model.engineType === 'pki';
}
get shouldHidePkiBetaModal() {
return localStorage.getItem('hidePkiBetaModal');
}
get windowOrigin() {
return window.location.origin;
}
@action
transitionToNewPki() {
this.router.transitionTo('vault.cluster.secrets.backend.pki.overview', this.args.model.id);
}
@action
toggleHidePkiBetaModal() {
this.hidePkiBetaModal = !this.hidePkiBetaModal;
this.hidePkiBetaModal
? localStorage.setItem('hidePkiBetaModal', true)
: localStorage.removeItem('hidePkiBetaModal');
}
}

View File

@ -54,33 +54,6 @@ const SECRET_BACKENDS = {
editComponent: 'role-aws-edit',
listItemPartial: 'secret-list/aws-role-item',
},
pki: {
displayName: 'PKI',
navigateTree: false,
listItemPartial: 'secret-list/pki-role-item',
tabs: [
{
name: 'roles',
label: 'Roles',
searchPlaceholder: 'Filter roles',
item: 'role',
create: 'Create role',
editComponent: 'pki/role-pki-edit',
},
{
name: 'cert',
modelPrefix: 'cert/',
label: 'Certificates',
searchPlaceholder: 'Filter certificates',
item: 'certificate',
message: 'Issue a certificate from a role.',
create: 'Create role',
tab: 'cert',
listItemPartial: 'secret-list/pki-cert-item',
editComponent: 'pki/pki-cert-show',
},
],
},
ssh: {
displayName: 'SSH',
searchPlaceholder: 'Filter roles',

View File

@ -27,7 +27,7 @@ export default class PkiEngine extends Engine {
'store',
'version',
],
externalRoutes: ['secrets', 'secretsListRoot', 'secretsListRootConfiguration', 'externalMountIssuer'],
externalRoutes: ['secrets', 'secretsListRootConfiguration', 'externalMountIssuer'],
};
}

View File

@ -1,105 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentRouteName, currentURL, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import editPage from 'vault/tests/pages/secrets/backend/pki/edit-role';
import listPage from 'vault/tests/pages/secrets/backend/list';
import generatePage from 'vault/tests/pages/secrets/backend/pki/generate-cert';
import configPage from 'vault/tests/pages/settings/configure-secret-backends/pki/section-cert';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import authPage from 'vault/tests/pages/auth';
import { SELECTORS } from 'vault/tests/helpers/pki';
import { csr } from 'vault/tests/helpers/pki/values';
import { v4 as uuidv4 } from 'uuid';
module('Acceptance | secrets/pki/list?tab=cert', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
return authPage.login();
});
// important for this comment to stay here otherwise the formatting mangles the CSR
// prettier-ignore
const CSR = csr;
// mount, generate CA, nav to create role page
const setup = async (assert, action = 'issue') => {
const path = `pki-${uuidv4()}`;
const roleName = 'role';
await enablePage.enable('pki', path);
await settled();
await configPage.visit({ backend: path }).form.generateCA();
await settled();
await editPage.visitRoot({ backend: path });
await settled();
await editPage.createRole('role', 'example.com');
await settled();
await generatePage.visit({ backend: path, id: roleName, action });
await settled();
return path;
};
test('it issues a cert', async function (assert) {
assert.expect(10);
const mount = await setup(assert);
await settled();
await generatePage.issueCert('foo');
await settled();
assert.strictEqual(currentURL(), `/vault/secrets/${mount}/credentials/role?action=issue`);
assert.dom(SELECTORS.certificate).exists('displays masked certificate');
assert.dom(SELECTORS.commonName).exists('displays common name');
assert.dom(SELECTORS.issueDate).exists('displays issue date');
assert.dom(SELECTORS.expiryDate).exists('displays expiration date');
assert.dom(SELECTORS.issuingCa).exists('displays masked issuing CA');
assert.dom(SELECTORS.privateKey).exists('displays masked private key');
assert.dom(SELECTORS.serialNumber).exists('displays serial number');
assert.dom(SELECTORS.caChain).exists('displays the CA chain');
await generatePage.back();
await settled();
assert.notOk(generatePage.commonNameValue, 'the form is cleared');
});
test('it signs a csr', async function (assert) {
assert.expect(4);
await setup(assert, 'sign');
await settled();
await generatePage.sign('common', CSR);
await settled();
assert.ok(SELECTORS.certificate, 'displays masked certificate');
assert.ok(SELECTORS.commonName, 'displays common name');
assert.ok(SELECTORS.issuingCa, 'displays masked issuing CA');
assert.ok(SELECTORS.serialNumber, 'displays serial number');
});
test('it views a cert', async function (assert) {
assert.expect(12);
const path = await setup(assert);
await generatePage.issueCert('foo');
await settled();
await listPage.visitRoot({ backend: path, tab: 'cert' });
await settled();
assert.strictEqual(currentURL(), `/vault/secrets/${path}/list?tab=cert`);
assert.strictEqual(listPage.secrets.length, 2, 'lists certs');
await listPage.secrets.objectAt(0).click();
await settled();
assert.strictEqual(
currentRouteName(),
'vault.cluster.secrets.backend.show',
'navigates to the show page'
);
assert.dom(SELECTORS.certificate).exists('displays masked certificate');
assert.dom(SELECTORS.commonName).exists('displays common name');
assert.dom(SELECTORS.issueDate).exists('displays issue date');
assert.dom(SELECTORS.expiryDate).exists('displays expiration date');
assert.dom(SELECTORS.serialNumber).exists('displays serial number');
assert.dom(SELECTORS.revocationTime).doesNotExist('does not display revocation time of 0');
assert.dom(SELECTORS.issuingCa).doesNotExist('does not display empty issuing CA');
assert.dom(SELECTORS.caChain).doesNotExist('does not display empty CA chain');
assert.dom(SELECTORS.privateKey).doesNotExist('does not display empty private key');
});
});

View File

@ -1,67 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { click, currentRouteName, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import page from 'vault/tests/pages/secrets/backend/list';
import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
module('Acceptance | secrets/pki/list', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
this.uid = uuidv4();
return authPage.login();
});
const mountAndNav = async (uid) => {
const path = `pki-${uid}`;
await enablePage.enable('pki', path);
await page.visitRoot({ backend: path });
};
test('it renders an empty list', async function (assert) {
assert.expect(5);
await mountAndNav(this.uid);
assert.strictEqual(
currentRouteName(),
'vault.cluster.secrets.backend.list-root',
'redirects from the index'
);
assert.ok(page.createIsPresent, 'create button is present');
await click('[data-test-configuration-tab]');
assert.ok(page.configureIsPresent, 'configure button is present');
assert.strictEqual(page.tabs.length, 2, 'shows 2 tabs');
assert.ok(page.backendIsEmpty);
});
test('it navigates to the create page', async function (assert) {
assert.expect(1);
await mountAndNav(this.uid);
await page.create();
assert.strictEqual(
currentRouteName(),
'vault.cluster.secrets.backend.create-root',
'links to the create page'
);
});
test('it navigates to the configure page', async function (assert) {
assert.expect(1);
await mountAndNav(this.uid);
await click('[data-test-configuration-tab]');
await page.configure();
await settled();
assert.strictEqual(
currentRouteName(),
'vault.cluster.settings.configure-secret-backend.section',
'links to the configure page'
);
});
});

View File

@ -1,89 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentRouteName, settled, visit, waitUntil } from '@ember/test-helpers';
import { module, test, skip } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import editPage from 'vault/tests/pages/secrets/backend/pki/edit-role';
import showPage from 'vault/tests/pages/secrets/backend/pki/show';
import listPage from 'vault/tests/pages/secrets/backend/list';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import authPage from 'vault/tests/pages/auth';
module('Acceptance | secrets/pki/create', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
this.uid = uuidv4();
return authPage.login();
});
skip('it creates a role and redirects', async function (assert) {
const path = `pki-create-${this.uid}`;
await enablePage.enable('pki', path);
await settled();
await editPage.visitRoot({ backend: path });
await settled();
await editPage.createRole('role', 'example.com');
await settled();
assert.strictEqual(
currentRouteName(),
'vault.cluster.secrets.backend.show',
'redirects to the show page'
);
assert.dom('[data-test-edit-link="true"]').exists('shows the edit button');
assert.dom('[data-test-credentials-link="true"]').exists('shows the generate button');
assert.dom('[data-test-sign-link="true"]').exists('shows the sign button');
await showPage.visit({ backend: path, id: 'role' });
await settled();
await showPage.generateCert();
await settled();
assert.strictEqual(
currentRouteName(),
'vault.cluster.secrets.backend.credentials',
'navs to the credentials page'
);
await showPage.visit({ backend: path, id: 'role' });
await settled();
await visit(`/vault/secrets/${path}/credentials/role?action=sign`);
assert.strictEqual(
currentRouteName(),
'vault.cluster.secrets.backend.credentials',
'navs to the credentials page'
);
await listPage.visitRoot({ backend: path });
await settled();
assert.strictEqual(listPage.secrets.length, 1, 'shows role in the list');
const secret = listPage.secrets.objectAt(0);
await secret.menuToggle();
await settled();
assert.ok(listPage.menuItems.length > 0, 'shows links in the menu');
});
test('it deletes a role', async function (assert) {
const path = `pki-delete-${this.uid}`;
await enablePage.enable('pki', path);
await settled();
await editPage.visitRoot({ backend: path });
await settled();
await editPage.createRole('role', 'example.com');
await settled();
await showPage.visit({ backend: path, id: 'role' });
await settled();
await showPage.deleteRole();
await settled();
assert.ok(
await waitUntil(() => currentRouteName() === 'vault.cluster.secrets.backend.list-root'),
'redirects to list page'
);
assert.ok(listPage.backendIsEmpty, 'no roles listed');
});
});

View File

@ -8,7 +8,7 @@ import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import page from 'vault/tests/pages/settings/configure-secret-backends/pki/index';
import { visit } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import { create } from 'ember-cli-page-object';
@ -31,7 +31,7 @@ module('Acceptance | settings/configure/secrets/ssh', function (hooks) {
const path = `ssh-configure-${this.uid}`;
await enablePage.enable('ssh', path);
await settled();
await page.visit({ backend: path });
visit(`/vault/settings/secrets/configure/${path}`);
await settled();
assert.dom(SELECTORS.generateSigningKey).isChecked('generate_signing_key defaults to true');
await click(SELECTORS.generateSigningKey);

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentRouteName, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import page from 'vault/tests/pages/settings/configure-secret-backends/pki/index';
import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
module('Acceptance | settings/configure/secrets/pki', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
this.uid = uuidv4();
return authPage.login();
});
test('it redirects to the cert section', async function (assert) {
const path = `pki-cert-${this.uid}`;
await enablePage.enable('pki', path);
await settled();
await page.visit({ backend: path });
await settled();
assert.strictEqual(
currentRouteName(),
'vault.cluster.settings.configure-secret-backend.section',
'redirects from the index'
);
});
});

View File

@ -1,149 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentRouteName, settled, click } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import page from 'vault/tests/pages/settings/configure-secret-backends/pki/section-cert';
import { SELECTORS } from 'vault/tests/helpers/pki';
import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import { v4 as uuidv4 } from 'uuid';
module('Acceptance | settings/configure/secrets/pki/cert', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
return authPage.login();
});
//prettier-ignore
const PEM_BUNDLE = `-----BEGIN CERTIFICATE-----
MIIDGjCCAgKgAwIBAgIUFvnhb2nQ8+KNS3SzjlfYDMHGIRgwDQYJKoZIhvcNAQEL
BQAwDTELMAkGA1UEAxMCZmEwHhcNMTgwMTEwMTg1NDI5WhcNMTgwMjExMTg1NDU5
WjANMQswCQYDVQQDEwJmYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN2VtBn6EMlA4aYre/xoKHxlgNDxJnfSQWfs6yF/K201qPnt4QF9AXChatbmcKVn
OaURq+XEJrGVgF/u2lSos3NRZdhWVe8o3/sOetsGxcrd0gXAieOSmkqJjp27bYdl
uY3WsxhyiPvdfS6xz39OehsK/YCB6qCzwB4eEfSKqbkvfDL9sLlAiOlaoHC9pczf
6/FANKp35UDwInSwmq5vxGbnWk9zMkh5Jq6hjOWHZnVc2J8J49PYvkIM8uiHDgOE
w71T2xM5plz6crmZnxPCOcTKIdF7NTEP2lUfiqc9lONV9X1Pi4UclLPHJf5bwTmn
JaWgbKeY+IlF61/mgxzhC7cCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFLDtc6+HZN2lv60JSDAZq3+IHoq7MB8GA1Ud
IwQYMBaAFLDtc6+HZN2lv60JSDAZq3+IHoq7MA0GA1UdEQQGMASCAmZhMA0GCSqG
SIb3DQEBCwUAA4IBAQDVt6OddTV1MB0UvF5v4zL1bEB9bgXvWx35v/FdS+VGn/QP
cC2c4ZNukndyHhysUEPdqVg4+up1aXm4eKXzNmGMY/ottN2pEhVEWQyoIIA1tH0e
8Kv/bysYpHZKZuoGg5+mdlHS2p2Dh2bmYFyBLJ8vaeP83NpTs2cNHcmEvWh/D4UN
UmYDODRN4qh9xYruKJ8i89iMGQfbdcq78dCC4JwBIx3bysC8oF4lqbTYoYNVTnAi
LVqvLdHycEOMlqV0ecq8uMLhPVBalCmIlKdWNQFpXB0TQCsn95rCCdi7ZTsYk5zv
Q4raFvQrZth3Cz/X5yPTtQL78oBYrmHzoQKDFJ2z
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA3ZW0GfoQyUDhpit7/GgofGWA0PEmd9JBZ+zrIX8rbTWo+e3h
AX0BcKFq1uZwpWc5pRGr5cQmsZWAX+7aVKizc1Fl2FZV7yjf+w562wbFyt3SBcCJ
45KaSomOnbtth2W5jdazGHKI+919LrHPf056Gwr9gIHqoLPAHh4R9IqpuS98Mv2w
uUCI6VqgcL2lzN/r8UA0qnflQPAidLCarm/EZudaT3MySHkmrqGM5YdmdVzYnwnj
09i+Qgzy6IcOA4TDvVPbEzmmXPpyuZmfE8I5xMoh0Xs1MQ/aVR+Kpz2U41X1fU+L
hRyUs8cl/lvBOaclpaBsp5j4iUXrX+aDHOELtwIDAQABAoIBACLdk2Ei/9Eq7FaB
MRkeKoCoWASIbU0dQD1iAf1bTTH554Sr8WOSj89xFqaJy9+6xk864Jleq9f1diWi
J6h6gwH6JNRNgWgIPnX6aUpdXnH1RT6ydP/h6XUg/9fBzhIn53Jx/ewy2WsIBtJ6
F/QoHP50VD8MMibnIaubf6fCycHhc97u4BKM2QdnAugn1sWjSiTIoYmFw/3Ej8mB
bItLWZTg9oMASgCtDwPEstlKn7yPqirOJj+G/a+6sIcP2fynd0fISsfLZ0ovN+yW
d3SV3orC0RNj83GVwYykqwCc/3pP0mRfX9fl8DKbXusITqUiGL8LGb+H6YDDpbNU
5Fj7VwECgYEA5P6aIcGfCZayEJtHKlTCA2/KBkGTOP/0iNKWhntBQT/GK+bjmr+D
GO1zR8ZFEIRdlUA5MjC9wU2AQikgFQzzmtz604Wt34fDN2NFrxq8sWN7Hjr65Fjf
ivJ6faT5r5gcNEq3EM/GLF9oJH8M+B5ccFe9iXH8AbmZHOO0FZtYxIcCgYEA97dm
Kj1qyuKlINXKt4KXdYMuIT+Z3G1B92wNN9TY/eJZgCJ7zlNcinUF/OFbiGgsk4t+
P0yVMs8BENQML0TH4Gebf4HfnDFno4J1M9HDt6HSMhsLKyvFYjFvb8hF4SPrY1pF
wW3lM3zMMzAVi8044vRrTvxfxL8QJX+1Hesye1ECgYAT5/H8Fzm8+qWV/fmMu3t2
EwSr0I18uftG3Y+KNzKv+lw+ur50WEuMIjAQQDMGwYrlC4UtUMFeCV+p4KtSSSLw
Bl+jfY5kzQdyTCXll9xpSy2LrjLbIMKl8Hgnbezqj7176jbJtlYSy2RhL84vz2vX
tDjcttTiTYD62uxvqGZqBwKBgFQ3tPM9aDZL8coFBWN4cZfRHnjNT7kCKEA/KwtF
QPSn5LfMgXz3GGo2OO/tihoJGMac0TIiDkN03y7ieLYFU1L2xoYGGIjYvxx2+PPC
KCEhUf4Y9aYavoOQvQsq8p8FgDyJ71dAzoC/uAjbGygpgGKgqG71HHYeYxXsoh3m
3YXRAoGAE7MBnVJWiIN5s63gGz9f9V6k1dPLfcPE1I0KMM/SDOIV0oLMsYQecTTB
ZzkXwRCdcJARkaKulTfjby7+oGpQydP8iZr+CNKFxwf838UbhhsXHnN6rc62qzYD
BXUV2Uwtxf+QCphnlht9muX2fsLIzDJea0JipWj1uf2H8OZsjE8=
-----END RSA PRIVATE KEY-----`;
const mountAndNav = async (assert, prefix) => {
const path = `${prefix}pki-${uuidv4()}`;
await enablePage.enable('pki', path);
await settled();
await page.visit({ backend: path });
await settled();
return path;
};
test('cert config: generate', async function (assert) {
assert.expect(10);
await mountAndNav(assert);
await settled();
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.configure-secret-backend.section');
await page.form.generateCA();
await settled();
assert.dom(SELECTORS.certificate).exists('certificate is present and masked');
assert.dom(SELECTORS.commonName).exists('displays common name');
assert.dom(SELECTORS.issueDate).exists('displays issue date');
assert.dom(SELECTORS.expiryDate).exists('displays expiration date');
assert.dom(SELECTORS.issuingCa).exists('displays masked issuing CA');
assert.dom(SELECTORS.serialNumber).exists('displays serial number');
assert.dom(SELECTORS.csr).doesNotExist('does not display empty CSR');
assert.dom(SELECTORS.caChain).doesNotExist('does not display empty CA chain');
assert.dom(SELECTORS.privateKey).doesNotExist('does not display empty private key');
await page.form.back();
await page.form.generateCA();
await settled();
});
test('cert config: upload', async function (assert) {
assert.expect(2);
await mountAndNav(assert);
await settled();
assert.strictEqual(page.form.downloadLinks.length, 0, 'there are no download links');
await page.form.uploadCA(PEM_BUNDLE);
await settled();
assert.ok(
page.flash.latestMessage.startsWith('The certificate for this backend has been updated'),
'flash message displays properly'
);
});
test('cert config: sign intermediate and set signed intermediate', async function (assert) {
assert.expect(3);
const rootPath = await mountAndNav(assert, 'root-');
await page.form.generateCA();
await settled();
const intermediatePath = await mountAndNav(assert, 'intermediate-');
await page.form.generateCA('Intermediate CA', 'intermediate');
await settled();
// cache csr
await click('.masked-input-toggle');
const csrVal = document.querySelector('.masked-value').innerText;
await page.form.back();
await settled();
await page.visit({ backend: rootPath });
await settled();
await page.form.signIntermediate('Intermediate CA');
await settled();
await page.form.csrField(csrVal).submit();
await settled();
assert.dom(SELECTORS.caChain).exists('full CA chain is shown');
assert.dom(SELECTORS.privateKey).doesNotExist('does not display empty private key');
await click('.masked-input-toggle');
const intermediateCert = document.querySelector('[data-test-masked-input]').innerText;
await page.form.back();
await settled();
await page.visit({ backend: intermediatePath });
await settled();
await page.form.setSignedIntermediateBtn().signedIntermediate(intermediateCert);
await settled();
await page.form.submit();
await settled();
assert.dom('[data-test-go-replace-ca]').exists();
});
});

View File

@ -1,36 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentRouteName, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import page from 'vault/tests/pages/settings/configure-secret-backends/pki/section';
import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
module('Acceptance | settings/configure/secrets/pki/crl', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
this.uid = uuidv4();
return authPage.login();
});
test('it saves crl config', async function (assert) {
const path = `pki-crl-${this.uid}`;
await enablePage.enable('pki', path);
await settled();
await page.visit({ backend: path, section: 'crl' });
await settled();
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.configure-secret-backend.section');
await page.form.fillInUnit('h');
await page.form.fillInValue(3);
await page.form.submit();
await settled();
assert.strictEqual(page.lastMessage, 'The crl config for this backend has been updated.');
});
});

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentRouteName, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import page from 'vault/tests/pages/settings/configure-secret-backends/pki/section';
import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
module('Acceptance | settings/configure/secrets/pki/tidy', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
return authPage.login();
});
test('it saves tidy config', async function (assert) {
const path = `pki-tidy-${uuidv4()}`;
await enablePage.enable('pki', path);
await settled();
await page.visit({ backend: path, section: 'tidy' });
await settled();
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.configure-secret-backend.section');
await page.form.fields.objectAt(0).clickLabel();
await page.form.submit();
await settled();
assert.strictEqual(page.lastMessage, 'The tidy config for this backend has been updated.');
});
});

View File

@ -1,41 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentRouteName, settled, find, waitUntil } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import page from 'vault/tests/pages/settings/configure-secret-backends/pki/section';
import authPage from 'vault/tests/pages/auth';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
module('Acceptance | settings/configure/secrets/pki/urls', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
this.uid = uuidv4();
return authPage.login();
});
test('it saves urls config', async function (assert) {
const path = `pki-config-urls-${this.uid}`;
await enablePage.enable('pki', path);
await settled();
await page.visit({ backend: path, section: 'urls' });
await settled();
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.configure-secret-backend.section');
await page.form.fields.objectAt(0).textarea('foo').change();
await page.form.submit();
await waitUntil(() => find('[data-test-error]'));
assert.ok(page.form.hasError, 'shows error on invalid input');
await page.form.fields.objectAt(0).textarea('foo.example.com').change();
await page.form.submit();
await settled();
assert.strictEqual(page.lastMessage, 'The urls config for this backend has been updated.');
});
});

View File

@ -1,85 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { resolve } from 'rsvp';
import EmberObject from '@ember/object';
import Service from '@ember/service';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { create } from 'ember-cli-page-object';
import configPki from 'vault/tests/pages/components/pki/config-pki-ca';
import apiStub from 'vault/tests/helpers/noop-all-api-requests';
const component = create(configPki);
const storeStub = Service.extend({
createRecord(type, args) {
return EmberObject.create(args, {
save() {
return resolve(this);
},
destroyRecord() {},
send() {},
unloadRecord() {},
});
},
});
module('Integration | Component | config pki ca', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.server = apiStub();
this.owner.lookup('service:flash-messages').registerTypes(['success']);
this.owner.register('service:store', storeStub);
this.storeService = this.owner.lookup('service:store');
});
hooks.afterEach(function () {
this.server.shutdown();
});
const config = function (pem) {
return EmberObject.create({
pem: pem,
backend: 'pki',
caChain: 'caChain',
der: new Blob(['der'], { type: 'text/plain' }),
});
};
const setupAndRender = async function (context, onRefresh) {
const refreshFn = onRefresh || function () {};
context.set('config', config());
context.set('onRefresh', refreshFn);
await context.render(hbs`<Pki::ConfigPkiCa @onRefresh={{this.onRefresh}} @config={{this.config}} />`);
};
test('it renders, no pem', async function (assert) {
await setupAndRender(this);
assert.notOk(component.hasTitle, 'no title in the default state');
assert.strictEqual(component.replaceCAText, 'Configure CA');
assert.strictEqual(component.downloadLinks.length, 0, 'there are no download links');
await component.replaceCA();
assert.strictEqual(component.title, 'Configure CA Certificate');
await component.back();
await component.setSignedIntermediateBtn();
assert.strictEqual(component.title, 'Set signed intermediate');
});
test('it renders, with pem', async function (assert) {
const c = config('pem');
this.set('config', c);
await render(hbs`<Pki::ConfigPkiCa @config={{this.config}} />`);
assert.notOk(component.hasTitle, 'no title in the default state');
assert.strictEqual(component.replaceCAText, 'Add CA');
assert.strictEqual(component.downloadLinks.length, 3, 'shows download links');
});
});

View File

@ -1,154 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { resolve } from 'rsvp';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { create } from 'ember-cli-page-object';
import configPki from 'vault/tests/pages/components/pki/config-pki';
const component = create(configPki);
module('Integration | Component | config pki', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(async function () {
this.owner.lookup('service:flash-messages').registerTypes(['success']);
this.store = this.owner.lookup('service:store');
this.config = await this.store.createRecord('pki/pki-config');
this.mockConfigSave = function (saveFn) {
const { tidyAttrs, crlAttrs, urlsAttrs } = this.config;
return {
save: saveFn,
rollbackAttributes: () => {},
tidyAttrs,
crlAttrs,
urlsAttrs,
set: () => {},
};
};
});
const setupAndRender = async function (context, config, section = 'tidy') {
context.set('config', config);
context.set('section', section);
await context.render(hbs`<Pki::ConfigPki @section={{this.section}} @config={{this.config}} />`);
};
test('it renders tidy section', async function (assert) {
await setupAndRender(this, this.config);
assert.ok(component.text.startsWith('You can tidy up the backend'));
assert.notOk(component.hasTitle, 'No title for tidy section');
assert.strictEqual(component.fields.length, 3, 'renders all three tidy fields');
assert.ok(component.fields.objectAt(0).labelText, 'Tidy the Certificate Store');
assert.ok(component.fields.objectAt(1).labelText, 'Tidy the Revocation List (CRL)');
assert.ok(component.fields.objectAt(1).labelText, 'Safety buffer');
});
test('it renders crl section', async function (assert) {
await setupAndRender(this, this.config, 'crl');
assert.false(this.config.disable, 'CRL config defaults disable=false');
assert.ok(component.hasTitle, 'renders form title');
assert.strictEqual(component.title, 'Certificate Revocation List (CRL) config');
assert.ok(
component.text.startsWith('Set the duration for which the generated CRL'),
'renders form subtext'
);
assert
.dom('[data-test-ttl-form-label="CRL building enabled"]')
.hasText('CRL building enabled', 'renders enabled field title');
assert
.dom('[data-test-ttl-form-subtext]')
.hasText('The CRL will expire after', 'renders enabled field subtext');
assert.dom('[data-test-input="expiry"] input').isChecked('defaults to enabling CRL build');
assert.dom('[data-test-ttl-value="CRL building enabled"]').hasValue('3', 'default value is 3 (72h)');
assert.dom('[data-test-select="ttl-unit"]').hasValue('d', 'default unit value is days');
await click('[data-test-input="expiry"] input');
assert
.dom('[data-test-ttl-form-subtext]')
.hasText('The CRL will not be built.', 'renders disabled text when toggled off');
// assert 'disable' attr on pki-config model updates with toggle
assert.true(this.config.disable, 'when toggled off, sets CRL config to disable=true');
await click('[data-test-input="expiry"] input');
assert
.dom('[data-test-ttl-form-subtext]')
.hasText('The CRL will expire after', 'toggles back to enabled text');
assert.false(this.config.disable, 'CRL config toggles back to disable=false');
});
test('it renders urls section', async function (assert) {
await setupAndRender(this, this.config, 'urls');
assert.notOk(component.hasTitle, 'No title for urls section');
assert.strictEqual(component.fields.length, 3);
assert.ok(component.fields.objectAt(0).labelText, 'Issuing certificates');
assert.ok(component.fields.objectAt(1).labelText, 'CRL Distribution Points');
assert.ok(component.fields.objectAt(2).labelText, 'OCSP Servers');
});
test('it calls save with the correct arguments for tidy', async function (assert) {
assert.expect(3);
const section = 'tidy';
this.set('onRefresh', () => {
assert.ok(true, 'refresh called');
});
this.set(
'config',
this.mockConfigSave((options) => {
assert.strictEqual(options.adapterOptions.method, section, 'method passed to save');
assert.deepEqual(
options.adapterOptions.fields,
['tidyCertStore', 'tidyRevocationList', 'safetyBuffer'],
'tidy fields passed to save'
);
return resolve();
})
);
this.set('section', section);
await render(
hbs`<Pki::ConfigPki @section={{this.section}} @config={{this.config}} @onRefresh={{this.onRefresh}} />`
);
component.submit();
});
test('it calls save with the correct arguments for crl', async function (assert) {
assert.expect(3);
const section = 'crl';
this.set('onRefresh', () => {
assert.ok(true, 'refresh called');
});
this.set(
'config',
this.mockConfigSave((options) => {
assert.strictEqual(options.adapterOptions.method, section, 'method passed to save');
assert.deepEqual(options.adapterOptions.fields, ['expiry', 'disable'], 'CRL fields passed to save');
return resolve();
})
);
this.set('section', section);
await render(
hbs`<Pki::ConfigPki @section={{this.section}} @config={{this.config}} @onRefresh={{this.onRefresh}} />`
);
component.submit();
});
test('it correctly sets toggle when initial CRL config is disable=true', async function (assert) {
assert.expect(3);
// change default config attrs
const configDisabled = this.config;
configDisabled.expiry = '1m';
configDisabled.disable = true;
await setupAndRender(this, configDisabled, 'crl');
assert.dom('[data-test-input="expiry"] input').isNotChecked('toggle disabled when CRL config disabled');
await click('[data-test-input="expiry"] input');
assert
.dom('[data-test-ttl-value="CRL building enabled"]')
.hasValue('1', 'when toggled on shows last set expired value');
assert.dom('[data-test-select="ttl-unit"]').hasValue('m', 'when toggled back on shows last set unit');
});
});

View File

@ -5,7 +5,7 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click } from '@ember/test-helpers';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import { setupMirage } from 'ember-cli-mirage/test-support';
@ -28,7 +28,6 @@ module('Integration | Component | secret-list-header', function (hooks) {
<SecretListHeader
@model={{this.model}}
/>
<div id="modal-wormhole"></div>
`);
const selector = '[data-test-kv-version-badge]';
@ -44,91 +43,4 @@ module('Integration | Component | secret-list-header', function (hooks) {
}
}
});
test('it should render new pki beta button and remain the same for other engines', async function (assert) {
const backends = supportedSecretBackends();
const numExpects = backends.length + 1;
assert.expect(numExpects);
this.server.post('/sys/capabilities-self', () => {});
for (const type of backends) {
const data = this.server.create('secret-engine', 2, { type });
this.model = mirageToModels(data);
await render(hbs`
<SecretListHeader
@model={{this.model}}
/>
<div id="modal-wormhole"></div>
`);
const oldPkiBetaButtonSelector = '[data-test-old-pki-beta-button]';
const oldPkiBetaModalSelector = '[data-test-modal-background="New PKI Beta"]';
if (type === 'pki') {
assert.dom(oldPkiBetaButtonSelector).hasText('New PKI UI available');
await click(oldPkiBetaButtonSelector);
assert.dom(oldPkiBetaModalSelector).exists();
} else {
assert
.dom(oldPkiBetaButtonSelector)
.doesNotExist(`Version badge does not render for ${type} engine type`);
}
}
});
test('it should render return to old pki from new pki', async function (assert) {
const backends = supportedSecretBackends();
assert.expect(backends.length);
this.server.post('/sys/capabilities-self', () => {});
for (const type of backends) {
const data = this.server.create('secret-engine', 2, { type });
this.model = mirageToModels(data);
await render(hbs`
<SecretListHeader
@model={{this.model}}
@isEngine={{true}}
/>
<div id="modal-wormhole"></div>
`);
const newPkiButtonSelector = '[data-test-new-pki-beta-button]';
if (type === 'pki') {
assert.dom(newPkiButtonSelector).hasText('Return to old PKI');
} else {
assert.dom(newPkiButtonSelector).doesNotExist(`No return to old pki exists`);
}
}
});
test('it should show the pki modal when New PKI UI available button is clicked', async function (assert) {
const backends = supportedSecretBackends();
const numExpects = backends.length + 1;
assert.expect(numExpects);
this.server.post('/sys/capabilities-self', () => {});
for (const type of backends) {
const data = this.server.create('secret-engine', 2, { type });
this.model = mirageToModels(data);
await render(hbs`
<SecretListHeader
@model={{this.model}}
/>
<div id="modal-wormhole"></div>
`);
const oldPkiButtonSelector = '[data-test-old-pki-beta-button]';
const cancelPkiBetaModal = '[data-test-cancel-pki-beta-modal]';
if (type === 'pki') {
await click(oldPkiButtonSelector);
assert.dom('.modal.is-active').exists('Pki beta modal is open');
await click(cancelPkiBetaModal);
assert.dom('.modal').exists('Pki beta modal is closed');
} else {
assert.dom(oldPkiButtonSelector).doesNotExist(`No return to old pki exists`);
}
}
});
});

View File

@ -48,10 +48,8 @@ module('Integration | Util | parse pki certificate', function (hooks) {
country: 'France',
other_sans: '1.3.1.4.1.5.9.2.6;UTF8:some-utf-string',
exclude_cn_from_sans: true,
expiry_date: {},
ip_sans: '192.158.1.38, 1234:0fd2:5621:0001:0089:0000:0000:4500',
key_usage: 'CertSign, CRLSign',
issue_date: {},
locality: 'Paris',
max_path_length: 17,
not_valid_after: 1678210083,
@ -239,8 +237,6 @@ module('Integration | Util | parse pki certificate', function (hooks) {
common_name: null,
country: null,
exclude_cn_from_sans: false,
expiry_date: {},
issue_date: {},
key_usage: null,
locality: null,
max_path_length: 10,

View File

@ -1,54 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { clickable, collection, fillable, text, selectable, isPresent } from 'ember-cli-page-object';
import fields from '../form-field';
export default {
...fields,
scope: '.config-pki-ca',
text: text('[data-test-text]'),
title: text('[data-test-title]'),
hasTitle: isPresent('[data-test-title]'),
hasError: isPresent('[data-test-error]'),
hasSignIntermediateForm: isPresent('[data-test-sign-intermediate-form]'),
replaceCA: clickable('[data-test-go-replace-ca]'),
replaceCAText: text('[data-test-go-replace-ca]'),
setSignedIntermediateBtn: clickable('[data-test-go-set-signed-intermediate]'),
signIntermediateBtn: clickable('[data-test-go-sign-intermediate]'),
caType: selectable('[data-test-input="caType"]'),
submit: clickable('[data-test-submit]'),
back: clickable('[data-test-back-button]'),
signedIntermediate: fillable('[data-test-signed-intermediate]'),
downloadLinks: collection('[data-test-ca-download-link]'),
rows: collection('[data-test-table-row]'),
rowValues: collection('[data-test-row-value]'),
csr: text('[data-test-row-value="CSR"]', { normalize: false }),
csrField: fillable('[data-test-input="csr"]'),
certificate: text('[data-test-row-value="Certificate"]', { normalize: false }),
uploadCert: clickable('[data-test-input="uploadPemBundle"]'),
enterCertAsText: clickable('[data-test-text-toggle]'),
pemBundle: fillable('[data-test-text-file-textarea]'),
commonName: fillable('[data-test-input="commonName"]'),
async generateCA(commonName = 'PKI CA', type = 'root') {
if (type === 'intermediate') {
return await this.replaceCA().commonName(commonName).caType('intermediate').submit();
}
return await this.replaceCA().commonName(commonName).submit();
},
async uploadCA(pem) {
return await this.replaceCA().uploadCert().enterCertAsText().pemBundle(pem).submit();
},
async signIntermediate(commonName) {
return await this.signIntermediateBtn().commonName(commonName);
},
};

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { clickable, fillable, text, isPresent } from 'ember-cli-page-object';
import fields from '../form-field';
export default {
...fields,
scope: '.config-pki',
text: text('[data-test-text]'),
title: text('[data-test-title]'),
hasTitle: isPresent('[data-test-title]'),
hasError: isPresent('[data-test-error]'),
submit: clickable('[data-test-submit]'),
enableTtl: clickable('[data-test-toggle-input]'),
fillInValue: fillable('[data-test-ttl-value]'),
fillInUnit: fillable('[data-test-select="ttl-unit"]'),
};

View File

@ -1,10 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { create, visitable } from 'ember-cli-page-object';
export default create({
visit: visitable('/vault/settings/secrets/configure/:backend/'),
});

View File

@ -1,15 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { create, visitable } from 'ember-cli-page-object';
import ConfigPKICA from 'vault/tests/pages/components/pki/config-pki-ca';
import flashMessages from 'vault/tests/pages/components/flash-message';
export default create({
visit: visitable('/vault/settings/secrets/configure/:backend/cert'),
form: ConfigPKICA,
flash: flashMessages,
});

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { create, visitable, collection } from 'ember-cli-page-object';
import { getter } from 'ember-cli-page-object/macros';
import ConfigPKI from 'vault/tests/pages/components/pki/config-pki';
export default create({
visit: visitable('/vault/settings/secrets/configure/:backend/:section'),
form: ConfigPKI,
lastMessage: getter(function () {
const count = this.flashMessages.length;
return this.flashMessages.objectAt(count - 1).text;
}),
flashMessages: collection('[data-test-flash-message-body]'),
});