mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Add GCP configuration details (#29247)
* starting * add the details functionality * test coverage * welp, friday fingers * small small changes * Update ui/app/models/gcp/config.js Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/helpers/mountable-secret-engines.js Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * update small changes on model * reorder loop on configuration details * Update ui/tests/integration/components/secret-engine/configuration-details-test.js Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/models/gcp/config.js Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/models/gcp/config.js Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/routes/vault/cluster/secrets/backend/configuration/index.js Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * add comment --------- Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
This commit is contained in:
26
ui/app/adapters/gcp/config.js
Normal file
26
ui/app/adapters/gcp/config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ApplicationAdapter from '../application';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
|
||||
export default class GcpConfig extends ApplicationAdapter {
|
||||
namespace = 'v1';
|
||||
|
||||
_url(backend) {
|
||||
return `${this.buildURL()}/${encodePath(backend)}/config`;
|
||||
}
|
||||
|
||||
queryRecord(store, type, query) {
|
||||
const { backend } = query;
|
||||
return this.ajax(this._url(backend), 'GET').then((resp) => {
|
||||
return {
|
||||
...resp,
|
||||
id: backend,
|
||||
backend,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,15 @@
|
||||
@title="{{@typeDisplay}} not configured"
|
||||
@message="Get started by configuring your {{@typeDisplay}} secrets engine."
|
||||
>
|
||||
<Hds::Link::Standalone
|
||||
@icon="chevron-right"
|
||||
@iconPosition="trailing"
|
||||
@text="Configure {{@typeDisplay}}"
|
||||
@route="vault.cluster.secrets.backend.configuration.edit"
|
||||
@model={{@id}}
|
||||
/>
|
||||
{{! TODO: short-term conditional to be removed once configuration for gcp is merged. }}
|
||||
{{#unless (eq @typeDisplay "Google Cloud")}}
|
||||
<Hds::Link::Standalone
|
||||
@icon="chevron-right"
|
||||
@iconPosition="trailing"
|
||||
@text="Configure {{@typeDisplay}}"
|
||||
@route="vault.cluster.secrets.backend.configuration.edit"
|
||||
@model={{@id}}
|
||||
/>
|
||||
{{/unless}}
|
||||
</EmptyState>
|
||||
{{/each}}
|
||||
@@ -142,8 +142,7 @@ export function wifEngines() {
|
||||
}
|
||||
|
||||
// The UI only supports configuration views for these secrets engines. The CLI must be used to manage other engine resources (i.e. roles, credentials).
|
||||
// Will eventually include gcp.
|
||||
export const CONFIGURATION_ONLY = ['azure'];
|
||||
export const CONFIGURATION_ONLY = ['azure', 'gcp'];
|
||||
|
||||
export function configurationOnly() {
|
||||
return CONFIGURATION_ONLY.slice();
|
||||
@@ -151,7 +150,7 @@ export function configurationOnly() {
|
||||
|
||||
// Secret engines that have their own configuration page and actions
|
||||
// These engines do not exist in their own Ember engine.
|
||||
export const CONFIGURABLE_SECRET_ENGINES = ['aws', 'azure', 'ssh'];
|
||||
export const CONFIGURABLE_SECRET_ENGINES = ['aws', 'azure', 'gcp', 'ssh'];
|
||||
|
||||
export function configurableSecretEngines() {
|
||||
return CONFIGURABLE_SECRET_ENGINES.slice();
|
||||
@@ -161,7 +160,7 @@ export function mountableEngines() {
|
||||
return MOUNTABLE_SECRET_ENGINES.slice();
|
||||
}
|
||||
// secret engines that have not other views than the mount view and mount details view
|
||||
export const UNSUPPORTED_ENGINES = ['alicloud', 'consul', 'gcp', 'gcpkms', 'nomad', 'rabbitmq', 'totp'];
|
||||
export const UNSUPPORTED_ENGINES = ['alicloud', 'consul', 'gcpkms', 'nomad', 'rabbitmq', 'totp'];
|
||||
|
||||
export function unsupportedEngines() {
|
||||
return UNSUPPORTED_ENGINES.slice();
|
||||
|
||||
@@ -10,6 +10,7 @@ const SUPPORTED_SECRET_BACKENDS = [
|
||||
'azure',
|
||||
'cubbyhole',
|
||||
'database',
|
||||
'gcp',
|
||||
'generic',
|
||||
'keymgmt',
|
||||
'kmip',
|
||||
|
||||
71
ui/app/models/gcp/config.js
Normal file
71
ui/app/models/gcp/config.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
|
||||
export default class GcpConfig extends Model {
|
||||
@attr('string') backend; // dynamic path of secret -- set on response from value passed to queryRecord
|
||||
|
||||
/* GCP config fields */
|
||||
@attr({
|
||||
label: 'Config TTL',
|
||||
editType: 'ttl',
|
||||
helperTextDisabled: 'The TTL (time-to-live) of generated tokens.',
|
||||
})
|
||||
ttl;
|
||||
|
||||
@attr({
|
||||
label: 'Max TTL',
|
||||
editType: 'ttl',
|
||||
helperTextDisabled:
|
||||
'Specifies the maximum config TTL (time-to-live) for long-lived credentials (i.e. service account keys).',
|
||||
})
|
||||
maxTtl;
|
||||
|
||||
@attr('string', {
|
||||
label: 'JSON credentials',
|
||||
subText:
|
||||
'If empty, Vault will use the GOOGLE_APPLICATION_CREDENTIALS environment variable if configured.',
|
||||
editType: 'file',
|
||||
docLink: '/vault/docs/secrets/gcp#authentication',
|
||||
})
|
||||
credentials; // obfuscated, never returned by API.
|
||||
|
||||
/* WIF config fields */
|
||||
@attr('string', {
|
||||
subText:
|
||||
'The audience claim value for plugin identity tokens. Must match an allowed audience configured for the target IAM OIDC identity provider.',
|
||||
})
|
||||
identityTokenAudience;
|
||||
|
||||
@attr({
|
||||
label: 'Identity token TTL',
|
||||
helperTextDisabled:
|
||||
'The TTL of generated tokens. Defaults to 1 hour, toggle on to specify a different value.',
|
||||
helperTextEnabled: 'The TTL of generated tokens.',
|
||||
editType: 'ttl',
|
||||
})
|
||||
identityTokenTtl;
|
||||
|
||||
@attr('string', {
|
||||
subText: 'Email ID for the Service Account to impersonate for Workload Identity Federation.',
|
||||
})
|
||||
serviceAccountEmail;
|
||||
|
||||
configurableParams = [
|
||||
'credentials',
|
||||
'serviceAccountEmail',
|
||||
'ttl',
|
||||
'maxTtl',
|
||||
'identityTokenAudience',
|
||||
'identityTokenTtl',
|
||||
];
|
||||
|
||||
get displayAttrs() {
|
||||
const formFields = expandAttributeMeta(this, this.configurableParams);
|
||||
return formFields.filter((attr) => attr.name !== 'credentials');
|
||||
}
|
||||
}
|
||||
@@ -63,13 +63,16 @@ export default class SecretsBackendConfigurationRoute extends Route {
|
||||
}
|
||||
|
||||
fetchConfig(type, id) {
|
||||
// id is the path where the backend is mounted since there's only one config per engine (often this path is referred to just as backend)
|
||||
switch (type) {
|
||||
case 'aws':
|
||||
return this.fetchAwsConfigs(id);
|
||||
case 'ssh':
|
||||
return this.fetchSshCaConfig(id);
|
||||
case 'azure':
|
||||
return this.fetchAzureConfig(id);
|
||||
case 'gcp':
|
||||
return this.fetchGcpConfig(id);
|
||||
case 'ssh':
|
||||
return this.fetchSshCaConfig(id);
|
||||
default:
|
||||
return reject({ httpStatus: 404, message: 'not found', path: id });
|
||||
}
|
||||
@@ -104,28 +107,6 @@ export default class SecretsBackendConfigurationRoute extends Route {
|
||||
}
|
||||
}
|
||||
|
||||
async fetchIssuer() {
|
||||
try {
|
||||
return await this.store.queryRecord('identity/oidc/config', {});
|
||||
} catch (e) {
|
||||
// silently fail if the endpoint is not available or the user doesn't have permission to access it.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchSshCaConfig(id) {
|
||||
try {
|
||||
return await this.store.queryRecord('ssh/ca-config', { backend: id });
|
||||
} catch (e) {
|
||||
if (e.httpStatus === 400 && e.errors[0] === `keys haven't been configured yet`) {
|
||||
// When first mounting a SSH engine it throws a 400 error with this specific message.
|
||||
// We want to catch this situation and return nothing so that the component can handle it correctly.
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAzureConfig(id) {
|
||||
try {
|
||||
const azureModel = await this.store.queryRecord('azure/config', { backend: id });
|
||||
@@ -149,6 +130,49 @@ export default class SecretsBackendConfigurationRoute extends Route {
|
||||
}
|
||||
}
|
||||
|
||||
async fetchGcpConfig(id) {
|
||||
try {
|
||||
const gcpModel = await this.store.queryRecord('gcp/config', { backend: id });
|
||||
let issuer = null;
|
||||
if (this.version.isEnterprise) {
|
||||
const WIF_FIELDS = ['identityTokenAudience', 'identityTokenTtl', 'serviceAccountEmail'];
|
||||
WIF_FIELDS.some((field) => gcpModel[field]) ? (issuer = await this.fetchIssuer()) : null;
|
||||
}
|
||||
const configArray = [];
|
||||
if (gcpModel) configArray.push(gcpModel);
|
||||
if (issuer) configArray.push(issuer);
|
||||
return configArray;
|
||||
} catch (e) {
|
||||
if (e.httpStatus === 404) {
|
||||
// a 404 error is thrown when GCP's config hasn't been set yet.
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchIssuer() {
|
||||
try {
|
||||
return await this.store.queryRecord('identity/oidc/config', {});
|
||||
} catch (e) {
|
||||
// silently fail if the endpoint is not available or the user doesn't have permission to access it.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchSshCaConfig(id) {
|
||||
try {
|
||||
return await this.store.queryRecord('ssh/ca-config', { backend: id });
|
||||
} catch (e) {
|
||||
if (e.httpStatus === 400 && e.errors[0] === `keys haven't been configured yet`) {
|
||||
// When first mounting a SSH engine it throws a 400 error with this specific message.
|
||||
// We want to catch this situation and return nothing so that the component can handle it correctly.
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
controller.typeDisplay = allEngines().find(
|
||||
|
||||
23
ui/app/serializers/gcp/config.js
Normal file
23
ui/app/serializers/gcp/config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ApplicationSerializer from '../application';
|
||||
|
||||
export default class GcpConfigSerializer extends ApplicationSerializer {
|
||||
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
|
||||
if (!payload.data) {
|
||||
return super.normalizeResponse(...arguments);
|
||||
}
|
||||
|
||||
const normalizedPayload = {
|
||||
id: payload.id,
|
||||
backend: payload.backend,
|
||||
data: {
|
||||
...payload.data,
|
||||
},
|
||||
};
|
||||
return super.normalizeResponse(store, primaryModelClass, normalizedPayload, id, requestType);
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,20 @@
|
||||
<SecretListHeader @model={{this.model.secretEngineModel}} @isConfigure={{true}} />
|
||||
|
||||
{{#if this.isConfigurable}}
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink
|
||||
@route="vault.cluster.secrets.backend.configuration.edit"
|
||||
@model={{this.model.secretEngineModel.id}}
|
||||
data-test-secret-backend-configure
|
||||
>
|
||||
Configure
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{! TODO: short-term conditional to be removed once configuration for gcp is merged. }}
|
||||
{{#unless (eq this.typeDisplay "Google Cloud")}}
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink
|
||||
@route="vault.cluster.secrets.backend.configuration.edit"
|
||||
@model={{this.model.secretEngineModel.id}}
|
||||
data-test-secret-backend-configure
|
||||
>
|
||||
Configure
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{/unless}}
|
||||
|
||||
<SecretEngine::ConfigurationDetails
|
||||
@configModels={{this.model.configModels}}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { click, visit, currentURL } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { runCmd } from 'vault/tests/helpers/commands';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { overrideResponse } from 'vault/tests/helpers/stubs';
|
||||
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
|
||||
import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
|
||||
import {
|
||||
expectedConfigKeys,
|
||||
expectedValueOfConfigKeys,
|
||||
configUrl,
|
||||
} from 'vault/tests/helpers/secret-engine/secret-engine-helpers';
|
||||
|
||||
module('Acceptance | GCP | configuration', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.uid = uuidv4();
|
||||
this.type = 'gcp';
|
||||
return authPage.login();
|
||||
});
|
||||
module('isEnterprise', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version.type = 'enterprise';
|
||||
});
|
||||
|
||||
test('it should show empty state and navigate to configuration view after mounting the GCP engine', async function (assert) {
|
||||
const path = `GCP-${this.uid}`;
|
||||
await visit('/vault/settings/mount-secret-backend');
|
||||
await mountBackend(this.type, path);
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/configuration`,
|
||||
'navigated to configuration view'
|
||||
);
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('Google Cloud not configured');
|
||||
assert.dom(GENERAL.emptyStateActions).doesNotContainText('Configure GCP');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
|
||||
test('it should not show "Configure" from toolbar', async function (assert) {
|
||||
const path = `GCP-${this.uid}`;
|
||||
await enablePage.enable(this.type, path);
|
||||
assert.dom(SES.configure).doesNotExist('Configure button does not exist.');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
|
||||
test('it should show configuration details with WIF options configured', async function (assert) {
|
||||
const path = `GCP-${this.uid}`;
|
||||
const wifAttrs = {
|
||||
service_account_email: 'service-email',
|
||||
identity_token_audience: 'audience',
|
||||
identity_token_ttl: 720000,
|
||||
};
|
||||
this.server.get(`${path}/config`, () => {
|
||||
assert.ok(true, 'request made to config when navigating to the configuration page.');
|
||||
return { data: { id: path, type: this.type, ...wifAttrs } };
|
||||
});
|
||||
await enablePage.enable(this.type, path);
|
||||
for (const key of expectedConfigKeys('gcp-wif')) {
|
||||
const responseKeyAndValue = expectedValueOfConfigKeys(this.type, key);
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue(key))
|
||||
.hasText(responseKeyAndValue, `value for ${key} on the ${this.type} config details exists.`);
|
||||
}
|
||||
// check mount configuration details are present and accurate.
|
||||
await click(SES.configurationToggle);
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Path'))
|
||||
.hasText(`${path}/`, 'mount path is displayed in the configuration details');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
|
||||
test('it should show configuration details with GCP account options configured', async function (assert) {
|
||||
const path = `GCP-${this.uid}`;
|
||||
const GCPAccountAttrs = {
|
||||
credentials: '{"some-key":"some-value"}',
|
||||
ttl: '1 hour',
|
||||
max_ttl: '4 hours',
|
||||
};
|
||||
this.server.get(`${path}/config`, () => {
|
||||
assert.ok(true, 'request made to config when navigating to the configuration page.');
|
||||
return { data: { id: path, type: this.type, ...GCPAccountAttrs } };
|
||||
});
|
||||
await enablePage.enable(this.type, path);
|
||||
for (const key of expectedConfigKeys(this.type)) {
|
||||
const responseKeyAndValue = expectedValueOfConfigKeys(this.type, key);
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue(key))
|
||||
.hasText(responseKeyAndValue, `value for ${key} on the ${this.type} config details exists.`);
|
||||
}
|
||||
// check mount configuration details are present and accurate.
|
||||
await click(SES.configurationToggle);
|
||||
assert
|
||||
.dom(GENERAL.infoRowValue('Path'))
|
||||
.hasText(`${path}/`, 'mount path is displayed in the configuration details');
|
||||
// cleanup
|
||||
await runCmd(`delete sys/mounts/${path}`);
|
||||
});
|
||||
|
||||
test('it should show API error when configuration read fails', async function (assert) {
|
||||
assert.expect(1);
|
||||
const path = `GCP-${this.uid}`;
|
||||
// interrupt get and return API error
|
||||
this.server.get(configUrl(this.type, path), () => {
|
||||
return overrideResponse(400, { errors: ['bad request'] });
|
||||
});
|
||||
await enablePage.enable(this.type, path);
|
||||
assert.dom(SES.error.title).hasText('Error', 'shows the secrets backend error route');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -148,6 +148,35 @@ const createAzureConfig = (store, backend, accessType = 'generic') => {
|
||||
return store.peekRecord('azure/config', backend);
|
||||
};
|
||||
|
||||
const createGcpConfig = (store, backend, accessType = 'gcp') => {
|
||||
// clear any records first
|
||||
store.unloadAll('gcp/config');
|
||||
if (accessType === 'wif') {
|
||||
store.pushPayload('gcp/config', {
|
||||
id: backend,
|
||||
modelName: 'gcp/config',
|
||||
data: {
|
||||
backend,
|
||||
service_account_email: 'service-email',
|
||||
identity_token_audience: 'audience',
|
||||
identity_token_ttl: 7200,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
store.pushPayload('gcp/config', {
|
||||
id: backend,
|
||||
modelName: 'gcp/config',
|
||||
data: {
|
||||
backend,
|
||||
credentials: '{"some-key":"some-value"}',
|
||||
ttl: '1 hour',
|
||||
max_ttl: '4 hours',
|
||||
},
|
||||
});
|
||||
}
|
||||
return store.peekRecord('gcp/config', backend);
|
||||
};
|
||||
|
||||
export function configUrl(type, backend) {
|
||||
switch (type) {
|
||||
case 'aws':
|
||||
@@ -181,6 +210,8 @@ export const createConfig = (store, backend, type) => {
|
||||
return createAzureConfig(store, backend, 'wif');
|
||||
case 'azure-generic':
|
||||
return createAzureConfig(store, backend, 'generic');
|
||||
case 'gcp':
|
||||
return createGcpConfig(store, backend);
|
||||
}
|
||||
};
|
||||
// Used in tests to assert the expected keys in the config details of configurable secret engines
|
||||
@@ -220,6 +251,12 @@ export const expectedConfigKeys = (type) => {
|
||||
'identityTokenAudience',
|
||||
'Identity token TTL',
|
||||
];
|
||||
case 'gcp':
|
||||
return ['Config TTL', 'Max TTL'];
|
||||
case 'gcp-wif':
|
||||
return ['Service account email', 'Identity token audience', 'Identity token TTL'];
|
||||
case 'gcp-wif-camelCase':
|
||||
return ['serviceAccountEmail', 'identityTokenAudience', 'Identity token TTL'];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -257,6 +294,23 @@ const valueOfAzureKeys = (string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const valueOfGcpKeys = (string) => {
|
||||
switch (string) {
|
||||
case 'Credentials':
|
||||
return '"{"some-key":"some-value"}",';
|
||||
case 'Service account email':
|
||||
return 'service-email';
|
||||
case 'Config TTL':
|
||||
return '1 hour';
|
||||
case 'Max TTL':
|
||||
return '4 hours';
|
||||
case 'Identity token audience':
|
||||
return 'audience';
|
||||
case 'Identity token TTL':
|
||||
return '8 days 8 hours';
|
||||
}
|
||||
};
|
||||
|
||||
const valueOfSshKeys = (string) => {
|
||||
switch (string) {
|
||||
case 'Public key':
|
||||
@@ -272,6 +326,8 @@ export const expectedValueOfConfigKeys = (type, string) => {
|
||||
return valueOfAwsKeys(string);
|
||||
case 'azure':
|
||||
return valueOfAzureKeys(string);
|
||||
case 'gcp':
|
||||
return valueOfGcpKeys(string);
|
||||
case 'ssh':
|
||||
return valueOfSshKeys(string);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
expectedValueOfConfigKeys,
|
||||
} from 'vault/tests/helpers/secret-engine/secret-engine-helpers';
|
||||
|
||||
const allEnginesArray = allEngines(); // saving as const so we don't invoke the method multiple times via the for loop
|
||||
|
||||
module('Integration | Component | SecretEngine/ConfigurationDetails', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
@@ -35,10 +37,8 @@ module('Integration | Component | SecretEngine/ConfigurationDetails', function (
|
||||
.hasText(`Get started by configuring your Display Name secrets engine.`);
|
||||
});
|
||||
|
||||
test('it shows config details if configModel(s) are passed in', async function (assert) {
|
||||
assert.expect(36);
|
||||
const allEnginesArray = allEngines(); // saving as const so we don't invoke the method multiple times via the for loop
|
||||
for (const type of CONFIGURABLE_SECRET_ENGINES) {
|
||||
for (const type of CONFIGURABLE_SECRET_ENGINES) {
|
||||
test(`it shows config details if configModel(s) are passed in for type: ${type}`, async function (assert) {
|
||||
const backend = `test-${type}`;
|
||||
this.configModels = createConfig(this.store, backend, type);
|
||||
this.typeDisplay = allEnginesArray.find((engine) => engine.type === type).displayName;
|
||||
@@ -60,6 +60,6 @@ module('Integration | Component | SecretEngine/ConfigurationDetails', function (
|
||||
assert.dom(GENERAL.infoRowValue(key)).doesNotHaveClass('masked-input', `${key} is not masked`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user