mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-28 04:10:44 -04:00
Merge remote-tracking branch 'remotes/from/ce/main'
Some checks failed
build / setup (push) Has been cancelled
build / hcp-setup (push) Has been cancelled
CI / setup (push) Has been cancelled
Run linters / Setup (push) Has been cancelled
Run linters / Semgrep (push) Has been cancelled
Check Copywrite Headers / copywrite (push) Has been cancelled
Security Scan / scan (push) Has been cancelled
build / Check ce/* Pull Requests (push) Has been cancelled
build / ui (push) Has been cancelled
build / artifacts-ce (push) Has been cancelled
build / artifacts-ent (push) Has been cancelled
build / hcp-image (push) Has been cancelled
build / test (push) Has been cancelled
build / test-hcp-image (push) Has been cancelled
build / completed-successfully (push) Has been cancelled
CI / Run Autopilot upgrade tool (push) Has been cancelled
CI / Run Go tests (push) Has been cancelled
CI / Run Go tests tagged with testonly (push) Has been cancelled
CI / Run Go tests with data race detection (push) Has been cancelled
CI / Run Go tests with FIPS configuration (push) Has been cancelled
CI / Test UI (push) Has been cancelled
CI / tests-completed (push) Has been cancelled
Run linters / Deprecated functions (push) Has been cancelled
Run linters / Code checks (push) Has been cancelled
Run linters / Protobuf generate delta (push) Has been cancelled
Run linters / Format (push) Has been cancelled
Some checks failed
build / setup (push) Has been cancelled
build / hcp-setup (push) Has been cancelled
CI / setup (push) Has been cancelled
Run linters / Setup (push) Has been cancelled
Run linters / Semgrep (push) Has been cancelled
Check Copywrite Headers / copywrite (push) Has been cancelled
Security Scan / scan (push) Has been cancelled
build / Check ce/* Pull Requests (push) Has been cancelled
build / ui (push) Has been cancelled
build / artifacts-ce (push) Has been cancelled
build / artifacts-ent (push) Has been cancelled
build / hcp-image (push) Has been cancelled
build / test (push) Has been cancelled
build / test-hcp-image (push) Has been cancelled
build / completed-successfully (push) Has been cancelled
CI / Run Autopilot upgrade tool (push) Has been cancelled
CI / Run Go tests (push) Has been cancelled
CI / Run Go tests tagged with testonly (push) Has been cancelled
CI / Run Go tests with data race detection (push) Has been cancelled
CI / Run Go tests with FIPS configuration (push) Has been cancelled
CI / Test UI (push) Has been cancelled
CI / tests-completed (push) Has been cancelled
Run linters / Deprecated functions (push) Has been cancelled
Run linters / Code checks (push) Has been cancelled
Run linters / Protobuf generate delta (push) Has been cancelled
Run linters / Format (push) Has been cancelled
This commit is contained in:
commit
eb5e22a029
10 changed files with 12 additions and 624 deletions
|
|
@ -1,179 +0,0 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ApplicationAdapter from '../application';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
import ControlGroupError from '../../lib/control-group-error';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
function pickKeys(obj, picklist) {
|
||||
const data = {};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (picklist.indexOf(key) >= 0) {
|
||||
data[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export default class KeymgmtKeyAdapter extends ApplicationAdapter {
|
||||
@service store;
|
||||
namespace = 'v1';
|
||||
|
||||
pathForType() {
|
||||
// backend name prepended in buildURL method
|
||||
return 'key';
|
||||
}
|
||||
|
||||
buildURL(modelName, id, snapshot, requestType, query) {
|
||||
let url = super.buildURL(...arguments);
|
||||
if (snapshot) {
|
||||
url = url.replace('key', `${snapshot.attr('backend')}/key`);
|
||||
} else if (query) {
|
||||
url = url.replace('key', `${query.backend}/key`);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
url(backend, id, type) {
|
||||
const url = `${this.buildURL()}/${backend}/key`;
|
||||
if (id) {
|
||||
if (type === 'ROTATE') {
|
||||
return url + '/' + encodePath(id) + '/rotate';
|
||||
} else if (type === 'PROVIDERS') {
|
||||
return url + '/' + encodePath(id) + '/kms';
|
||||
}
|
||||
return url + '/' + encodePath(id);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
_updateKey(backend, name, serialized) {
|
||||
// Only these two attributes are allowed to be updated
|
||||
const data = pickKeys(serialized, ['deletion_allowed', 'min_enabled_version']);
|
||||
return this.ajax(this.url(backend, name), 'PUT', { data });
|
||||
}
|
||||
|
||||
_createKey(backend, name, serialized) {
|
||||
// Only type is allowed on create
|
||||
const data = pickKeys(serialized, ['type']);
|
||||
return this.ajax(this.url(backend, name), 'POST', { data });
|
||||
}
|
||||
|
||||
async createRecord(store, type, snapshot) {
|
||||
const data = store.serializerFor(type.modelName).serialize(snapshot);
|
||||
const name = snapshot.attr('name');
|
||||
const backend = snapshot.attr('backend');
|
||||
// Keys must be created and then updated
|
||||
await this._createKey(backend, name, data);
|
||||
if (snapshot.attr('deletionAllowed')) {
|
||||
try {
|
||||
await this._updateKey(backend, name, data);
|
||||
} catch {
|
||||
throw new Error(`Key ${name} was created, but not all settings were saved`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
...data,
|
||||
id: name,
|
||||
backend,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
updateRecord(store, type, snapshot) {
|
||||
const data = store.serializerFor(type.modelName).serialize(snapshot);
|
||||
const name = snapshot.attr('name');
|
||||
const backend = snapshot.attr('backend');
|
||||
return this._updateKey(backend, name, data);
|
||||
}
|
||||
|
||||
distribute(backend, kms, key, data) {
|
||||
return this.ajax(`${this.buildURL()}/${backend}/kms/${encodePath(kms)}/key/${encodePath(key)}`, 'PUT', {
|
||||
data: { ...data },
|
||||
});
|
||||
}
|
||||
|
||||
async getProvider(backend, name) {
|
||||
try {
|
||||
const resp = await this.ajax(this.url(backend, name, 'PROVIDERS'), 'GET', {
|
||||
data: {
|
||||
list: true,
|
||||
},
|
||||
});
|
||||
return resp.data.keys ? resp.data.keys[0] : null;
|
||||
} catch (e) {
|
||||
if (e.httpStatus === 404) {
|
||||
// No results, not distributed yet
|
||||
return null;
|
||||
} else if (e.httpStatus === 403) {
|
||||
return { permissionsError: true };
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
getDistribution(backend, kms, key) {
|
||||
const url = `${this.buildURL()}/${backend}/kms/${kms}/key/${key}`;
|
||||
return this.ajax(url, 'GET')
|
||||
.then((res) => {
|
||||
return {
|
||||
...res.data,
|
||||
purposeArray: res.data.purpose.split(','),
|
||||
};
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e instanceof ControlGroupError) {
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
async queryRecord(store, type, query) {
|
||||
const { id, backend, recordOnly = false } = query;
|
||||
const keyData = await this.ajax(this.url(backend, id), 'GET');
|
||||
keyData.data.id = id;
|
||||
keyData.data.backend = backend;
|
||||
let provider, distribution;
|
||||
if (!recordOnly) {
|
||||
provider = await this.getProvider(backend, id);
|
||||
if (provider && !provider.permissionsError) {
|
||||
distribution = await this.getDistribution(backend, provider, id);
|
||||
}
|
||||
}
|
||||
return { ...keyData, provider, distribution };
|
||||
}
|
||||
|
||||
async query(store, type, query) {
|
||||
const { backend, provider } = query;
|
||||
const providerAdapter = store.adapterFor('keymgmt/provider');
|
||||
const url = provider ? providerAdapter.buildKeysURL(query) : this.url(backend);
|
||||
|
||||
return this.ajax(url, 'GET', {
|
||||
data: {
|
||||
list: true,
|
||||
},
|
||||
}).then((res) => {
|
||||
res.backend = backend;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
async rotateKey(backend, id) {
|
||||
const keyModel = this.store.peekRecord('keymgmt/key', id);
|
||||
const result = await this.ajax(this.url(backend, id, 'ROTATE'), 'PUT');
|
||||
await keyModel.reload();
|
||||
return result;
|
||||
}
|
||||
|
||||
removeFromProvider(model) {
|
||||
const url = `${this.buildURL()}/${model.backend}/kms/${model.provider}/key/${model.name}`;
|
||||
return this.ajax(url, 'DELETE').then(() => {
|
||||
model.provider = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ApplicationAdapter from '../application';
|
||||
import { all } from 'rsvp';
|
||||
|
||||
export default class KeymgmtKeyAdapter extends ApplicationAdapter {
|
||||
namespace = 'v1';
|
||||
listPayload = { data: { list: true } };
|
||||
|
||||
pathForType() {
|
||||
// backend name prepended in buildURL method
|
||||
return 'kms';
|
||||
}
|
||||
buildURL(modelName, id, snapshot, requestType, query) {
|
||||
let url = super.buildURL(...arguments);
|
||||
if (snapshot) {
|
||||
url = url.replace('kms', `${snapshot.attr('backend')}/kms`);
|
||||
} else if (query) {
|
||||
url = url.replace('kms', `${query.backend}/kms`);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
buildKeysURL(query) {
|
||||
const url = this.buildURL('keymgmt/provider', null, null, 'query', query);
|
||||
return `${url}/${query.provider}/key`;
|
||||
}
|
||||
async createRecord(store, { modelName }, snapshot) {
|
||||
// create uses PUT instead of POST
|
||||
const data = store.serializerFor(modelName).serialize(snapshot);
|
||||
const url = this.buildURL(modelName, snapshot.attr('name'), snapshot, 'updateRecord');
|
||||
return this.ajax(url, 'PUT', { data }).then(() => data);
|
||||
}
|
||||
findRecord(store, type, name) {
|
||||
return super.findRecord(...arguments).then((resp) => {
|
||||
resp.data = { ...resp.data, name };
|
||||
return resp;
|
||||
});
|
||||
}
|
||||
async query(store, type, query) {
|
||||
const { backend } = query;
|
||||
const url = this.buildURL(type.modelName, null, null, 'query', query);
|
||||
return this.ajax(url, 'GET', this.listPayload).then(async (resp) => {
|
||||
// additional data is needed to fullfil the list view requirements
|
||||
// pull in full record for listed items
|
||||
const records = await all(
|
||||
resp.data.keys.map((name) => this.findRecord(store, type, name, this._mockSnapshot(query.backend)))
|
||||
);
|
||||
resp.data.keys = records.map((record) => record.data);
|
||||
resp.backend = backend;
|
||||
return resp;
|
||||
});
|
||||
}
|
||||
async queryRecord(store, type, query) {
|
||||
return this.findRecord(store, type, query.id, this._mockSnapshot(query.backend));
|
||||
}
|
||||
|
||||
// when using find in query or queryRecord overrides snapshot is not available
|
||||
// ultimately buildURL requires the snapshot to pull the backend name for the dynamic segment
|
||||
// since we have the backend value from the query generate a mock snapshot
|
||||
_mockSnapshot(backend) {
|
||||
return {
|
||||
attr(prop) {
|
||||
return prop === 'backend' ? backend : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import { KeyManagementUpdateKeyRequestTypeEnum } from '@hashicorp/vault-client-typescript';
|
||||
|
||||
const KEY_TYPES = Object.values(KeyManagementUpdateKeyRequestTypeEnum);
|
||||
export default class KeymgmtKeyModel extends Model {
|
||||
@attr('string', {
|
||||
label: 'Key name',
|
||||
subText: 'This is the name of the key that shows in Vault.',
|
||||
})
|
||||
name;
|
||||
|
||||
@attr('string')
|
||||
backend;
|
||||
|
||||
@attr('string', {
|
||||
subText: 'The type of cryptographic key that will be created.',
|
||||
possibleValues: KEY_TYPES,
|
||||
defaultValue: 'rsa-2048',
|
||||
})
|
||||
type;
|
||||
|
||||
@attr('boolean', {
|
||||
label: 'Allow deletion',
|
||||
defaultValue: false,
|
||||
})
|
||||
deletionAllowed;
|
||||
|
||||
@attr('number', {
|
||||
label: 'Current version',
|
||||
})
|
||||
latestVersion;
|
||||
|
||||
@attr('number', {
|
||||
defaultValue: 0,
|
||||
defaultShown: 'All versions enabled',
|
||||
})
|
||||
minEnabledVersion;
|
||||
|
||||
@attr('array')
|
||||
versions;
|
||||
|
||||
// The following are calculated in serializer
|
||||
@attr('date')
|
||||
created;
|
||||
|
||||
@attr('date', {
|
||||
defaultShown: 'Not yet rotated',
|
||||
})
|
||||
lastRotated;
|
||||
|
||||
// The following are from endpoints other than the main read one
|
||||
@attr() provider; // string, or object with permissions error
|
||||
@attr() distribution;
|
||||
|
||||
icon = 'key';
|
||||
|
||||
get hasVersions() {
|
||||
return this.versions.length > 1;
|
||||
}
|
||||
|
||||
get createFields() {
|
||||
const createFields = ['name', 'type', 'deletionAllowed'];
|
||||
return expandAttributeMeta(this, createFields);
|
||||
}
|
||||
|
||||
get updateFields() {
|
||||
return expandAttributeMeta(this, ['minEnabledVersion', 'deletionAllowed']);
|
||||
}
|
||||
get showFields() {
|
||||
return expandAttributeMeta(this, [
|
||||
'name',
|
||||
'created',
|
||||
'type',
|
||||
'deletionAllowed',
|
||||
'latestVersion',
|
||||
'minEnabledVersion',
|
||||
'lastRotated',
|
||||
]);
|
||||
}
|
||||
|
||||
get keyTypeOptions() {
|
||||
return expandAttributeMeta(this, ['type'])[0];
|
||||
}
|
||||
|
||||
get distFields() {
|
||||
return [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
label: 'Distributed name',
|
||||
subText: 'The name given to the key by the provider.',
|
||||
},
|
||||
{ name: 'purpose', type: 'string', label: 'Key Purpose' },
|
||||
{ name: 'protection', type: 'string', subText: 'Where cryptographic operations are performed.' },
|
||||
];
|
||||
}
|
||||
|
||||
@lazyCapabilities(apiPath`${'backend'}/key/${'id'}`, 'backend', 'id') keyPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/key`, 'backend') keysPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/key/${'id'}/kms`, 'backend', 'id') keyProvidersPath;
|
||||
|
||||
get canCreate() {
|
||||
return this.keyPath.get('canCreate');
|
||||
}
|
||||
get canDelete() {
|
||||
return this.keyPath.get('canDelete');
|
||||
}
|
||||
get canEdit() {
|
||||
return this.keyPath.get('canUpdate');
|
||||
}
|
||||
get canRead() {
|
||||
return this.keyPath.get('canRead');
|
||||
}
|
||||
get canList() {
|
||||
return this.keysPath.get('canList');
|
||||
}
|
||||
get canListProviders() {
|
||||
return this.keyProvidersPath.get('canList');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
const CRED_PROPS = {
|
||||
azurekeyvault: ['client_id', 'client_secret', 'tenant_id'],
|
||||
awskms: ['access_key', 'secret_key', 'session_token', 'endpoint'],
|
||||
gcpckms: ['service_account_file'],
|
||||
};
|
||||
|
||||
const OPTIONAL_CRED_PROPS = ['session_token', 'endpoint'];
|
||||
|
||||
// since we have dynamic credential attributes based on provider we need a dynamic presence validator
|
||||
// add validators for all cred props and return true for value if not associated with selected provider
|
||||
const credValidators = Object.keys(CRED_PROPS).reduce((obj, providerKey) => {
|
||||
CRED_PROPS[providerKey].forEach((prop) => {
|
||||
if (!OPTIONAL_CRED_PROPS.includes(prop)) {
|
||||
obj[`credentials.${prop}`] = [
|
||||
{
|
||||
message: `${prop} is required`,
|
||||
validator(model) {
|
||||
return model.credentialProps.includes(prop) ? model.credentials[prop] : true;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const validations = {
|
||||
name: [{ type: 'presence', message: 'Provider name is required' }],
|
||||
keyCollection: [{ type: 'presence', message: 'Key Vault instance name' }],
|
||||
...credValidators,
|
||||
};
|
||||
|
||||
@withModelValidations(validations)
|
||||
export default class KeymgmtProviderModel extends Model {
|
||||
@service pagination;
|
||||
@attr('string') backend;
|
||||
@attr('string', {
|
||||
label: 'Provider name',
|
||||
subText: 'This is the name of the provider that will be displayed in Vault. This cannot be edited later.',
|
||||
})
|
||||
name;
|
||||
|
||||
@attr('string', {
|
||||
label: 'Type',
|
||||
subText: 'Choose the provider type.',
|
||||
possibleValues: ['azurekeyvault', 'awskms', 'gcpckms'],
|
||||
noDefault: true,
|
||||
})
|
||||
provider;
|
||||
|
||||
@attr('string', {
|
||||
label: 'Key Vault instance name',
|
||||
subText: 'The name of a Key Vault instance must be supplied. This cannot be edited later.',
|
||||
})
|
||||
keyCollection;
|
||||
|
||||
idPrefix = 'provider/';
|
||||
type = 'provider';
|
||||
|
||||
@tracked keys = [];
|
||||
@tracked credentials = null; // never returned from API -- set only during create/edit
|
||||
|
||||
get icon() {
|
||||
return {
|
||||
azurekeyvault: 'azure-color',
|
||||
awskms: 'aws-color',
|
||||
gcpckms: 'gcp-color',
|
||||
}[this.provider];
|
||||
}
|
||||
get typeName() {
|
||||
return {
|
||||
azurekeyvault: 'Azure Key Vault',
|
||||
awskms: 'AWS Key Management Service',
|
||||
gcpckms: 'Google Cloud Key Management Service',
|
||||
}[this.provider];
|
||||
}
|
||||
get showFields() {
|
||||
const attrs = expandAttributeMeta(this, ['name', 'keyCollection']);
|
||||
attrs.splice(1, 0, { hasBlock: true, label: 'Type', value: this.typeName, icon: this.icon });
|
||||
const l = this.keys.length;
|
||||
const value = l
|
||||
? `${l} ${l > 1 ? 'keys' : 'key'}`
|
||||
: this.canListKeys
|
||||
? 'None'
|
||||
: 'You do not have permission to list keys';
|
||||
attrs.push({ hasBlock: true, isLink: l, label: 'Keys', value });
|
||||
return attrs;
|
||||
}
|
||||
get credentialProps() {
|
||||
if (!this.provider) return [];
|
||||
return CRED_PROPS[this.provider];
|
||||
}
|
||||
get credentialFields() {
|
||||
const [creds, fields] = this.credentialProps.reduce(
|
||||
([creds, fields], prop) => {
|
||||
creds[prop] = null;
|
||||
const field = { name: `credentials.${prop}`, type: 'string', options: { label: prop } };
|
||||
if (prop === 'service_account_file') {
|
||||
field.options.subText = 'The path to a Google service account key file, not the file itself.';
|
||||
}
|
||||
fields.push(field);
|
||||
return [creds, fields];
|
||||
},
|
||||
[{}, []]
|
||||
);
|
||||
this.credentials = creds;
|
||||
return fields;
|
||||
}
|
||||
get createFields() {
|
||||
return expandAttributeMeta(this, ['provider', 'name', 'keyCollection']);
|
||||
}
|
||||
|
||||
async fetchKeys(page) {
|
||||
if (this.canListKeys === false) {
|
||||
this.keys = [];
|
||||
} else {
|
||||
// try unless capabilities returns false
|
||||
try {
|
||||
this.keys = await this.pagination.lazyPaginatedQuery('keymgmt/key', {
|
||||
backend: this.backend,
|
||||
provider: this.name,
|
||||
responsePath: 'data.keys',
|
||||
page: Number(page) || 1,
|
||||
});
|
||||
} catch (error) {
|
||||
this.keys = [];
|
||||
if (error.httpStatus !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@lazyCapabilities(apiPath`${'backend'}/kms/${'id'}`, 'backend', 'id') providerPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/kms`, 'backend') providersPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/kms/${'id'}/key`, 'backend', 'id') providerKeysPath;
|
||||
|
||||
get canCreate() {
|
||||
return this.providerPath.get('canCreate');
|
||||
}
|
||||
get canDelete() {
|
||||
return this.providerPath.get('canDelete');
|
||||
}
|
||||
get canEdit() {
|
||||
return this.providerPath.get('canUpdate');
|
||||
}
|
||||
get canRead() {
|
||||
return this.providerPath.get('canRead');
|
||||
}
|
||||
get canList() {
|
||||
return this.providersPath.get('canList');
|
||||
}
|
||||
get canListKeys() {
|
||||
return this.providerKeysPath.get('canList');
|
||||
}
|
||||
get canCreateKeys() {
|
||||
return this.providerKeysPath.get('canCreate');
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import { getEffectiveEngineType } from 'vault/utils/external-plugin-helpers';
|
|||
import { getKeymgmtProviderIcon } from 'vault/utils/keymgmt-provider-utils';
|
||||
import { getModelTypeForEngine } from 'vault/utils/model-helpers/secret-engine-helpers';
|
||||
import { normalizePath } from 'vault/utils/path-encoding-helpers';
|
||||
import { resolve } from 'rsvp';
|
||||
import {
|
||||
SecretsApiKeyManagementListKeysListEnum,
|
||||
SecretsApiKeyManagementListKmsProvidersListEnum,
|
||||
|
|
@ -96,6 +97,11 @@ export default Route.extend({
|
|||
return this.router.transitionTo('vault.cluster.secrets.backend.kv.list', backend);
|
||||
}
|
||||
const modelType = this.getModelType(effectiveType, tab);
|
||||
// Keymgmt routes use API-backed forms instead of Ember Data models, so skip model hydration.
|
||||
if (effectiveType === 'keymgmt') {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
return this.pathHelp.hydrateModel(modelType, backend).then(() => {
|
||||
this.store.unloadAll('capabilities');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ export default Route.extend({
|
|||
buildModel(secret, queryParams) {
|
||||
const backend = getEnginePathParam(this);
|
||||
const modelType = this.modelType(backend, secret, { queryParams });
|
||||
if (modelType === 'secret') {
|
||||
// Keymgmt resources are loaded through API-backed forms, so Ember Data hydration is unnecessary.
|
||||
if (modelType === 'secret' || modelType.startsWith('keymgmt/')) {
|
||||
return resolve();
|
||||
}
|
||||
return this.pathHelp.hydrateModel(modelType, backend);
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ApplicationSerializer from '../application';
|
||||
|
||||
export default class KeymgmtKeySerializer extends ApplicationSerializer {
|
||||
normalizeItems(payload) {
|
||||
const normalized = super.normalizeItems(payload);
|
||||
// Transform versions from object with number keys to array with key ids
|
||||
if (normalized.versions) {
|
||||
let lastRotated;
|
||||
let created;
|
||||
const versions = [];
|
||||
Object.keys(normalized.versions).forEach((key, i, arr) => {
|
||||
versions.push({
|
||||
id: parseInt(key, 10),
|
||||
...normalized.versions[key],
|
||||
});
|
||||
if (i === 0) {
|
||||
created = normalized.versions[key].creation_time;
|
||||
} else if (arr.length - 1 === i) {
|
||||
// Set lastRotated to the last key
|
||||
lastRotated = normalized.versions[key].creation_time;
|
||||
}
|
||||
});
|
||||
normalized.versions = versions;
|
||||
return { ...normalized, last_rotated: lastRotated, created };
|
||||
} else if (Array.isArray(normalized)) {
|
||||
return normalized.map((key) => ({
|
||||
id: key.id,
|
||||
name: key.id,
|
||||
backend: payload.backend,
|
||||
}));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ApplicationSerializer from '../application';
|
||||
|
||||
export default class KeymgmtProviderSerializer extends ApplicationSerializer {
|
||||
primaryKey = 'name';
|
||||
|
||||
normalizeItems(payload) {
|
||||
const normalized = super.normalizeItems(payload);
|
||||
if (Array.isArray(normalized)) {
|
||||
normalized.forEach((provider) => {
|
||||
provider.id = provider.name;
|
||||
provider.backend = payload.backend;
|
||||
});
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
serialize(snapshot) {
|
||||
const json = super.serialize(...arguments);
|
||||
return {
|
||||
...json,
|
||||
credentials: snapshot.record.credentials,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -40,19 +40,17 @@ module('Acceptance | Enterprise | keymgmt', function (hooks) {
|
|||
await runCmd([`delete sys/mounts/${engine.type}`]);
|
||||
});
|
||||
|
||||
// TODO: Fix this test - it requires proper provider capability mocking
|
||||
// This test is for provider functionality which still uses Ember Data
|
||||
// and is unrelated to the key migration to API service pattern
|
||||
test.skip('it should add new key and distribute to provider', async function (assert) {
|
||||
test('it should add new key and distribute to provider', async function (assert) {
|
||||
const path = `keymgmt-${Date.now()}`;
|
||||
this.server.post(`/${path}/key/test-key`, () => ({}));
|
||||
this.server.put(`/${path}/kms/test-keyvault/key/test-key`, () => ({}));
|
||||
this.server.post(`/${path}/kms/test-keyvault/key/test-key`, () => ({}));
|
||||
this.server.get(`/${path}/kms/test-keyvault/key`, () => ({ data: { keys: ['test-key'] } }));
|
||||
|
||||
await mountSecrets.enable('keymgmt', path);
|
||||
await click(SES.createSecretLink);
|
||||
await fillIn('[data-test-input="provider"]', 'azurekeyvault');
|
||||
await fillIn('[data-test-input="name"]', 'test-keyvault');
|
||||
await fillIn('[data-test-input="keyCollection"]', 'test-keycollection');
|
||||
await fillIn('[data-test-input="key_collection"]', 'test-keycollection');
|
||||
await fillIn('[data-test-input="credentials.client_id"]', '123');
|
||||
await fillIn('[data-test-input="credentials.client_secret"]', '456');
|
||||
await fillIn('[data-test-input="credentials.tenant_id"]', '789');
|
||||
|
|
@ -65,7 +63,6 @@ module('Acceptance | Enterprise | keymgmt', function (hooks) {
|
|||
await click('[data-test-operation="encrypt"]');
|
||||
await fillIn('[data-test-protection="hsm"]', 'hsm');
|
||||
|
||||
this.server.get(`/${path}/kms/test-keyvault/key`, () => ({ data: { keys: ['test-key'] } }));
|
||||
await click('[data-test-secret-save]');
|
||||
await click('[data-test-kms-provider-tab="keys"] a');
|
||||
assert.dom('[data-test-secret-link="test-key"]').exists('Key is listed under keys tab of provider');
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ module('Integration | Component | keymgmt/provider-edit', function (hooks) {
|
|||
|
||||
this.capabilities = { canDelete: true, canListKeys: true, canEdit: false, canCreateKeys: false };
|
||||
|
||||
this.server.post('/sys/capabilities-self', () => ({}));
|
||||
this.server.get('/keymgmt/kms/foo-bar/key', () => {
|
||||
return {
|
||||
data: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue