UI: Surface plugin version & cleanup utils (#31001)

* surface plugin version & removing mountable-auth-methods.js

* UI: Removing mountable-secret-engines.js (#30950)

* first pass, removing all related imports

* fix usage

* fix category

* fix typos

* fix more tests

* fix more tests pt2

* attempting WIF const removal

* fix wif tests, removing config consts

* fixing tests

* please

* removing fallback

* cleanup

* fix type ent test

* remove isaddon

* Revert "remove isaddon"

This reverts commit ee114197b7299711e35e3c8e5aca9694063726eb.

* adding tab click

* update case

* fix case, rename to isOnlyMountable

* fix backend form

* more test fix

* adding changelog

* pr comments

* renaming params, adding requiresADP

* updates

* updates and pr comments

* perhaps update the test
This commit is contained in:
Dan Rivera 2025-06-19 15:10:09 -04:00 committed by GitHub
parent 423d88af67
commit 008835ba36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 594 additions and 483 deletions

3
changelog/31001.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui/secrets: Display the plugin version on the secret engine list view. Move KV's version to a tooltip that appears when hovering over the engine's name.
```

View File

@ -49,7 +49,7 @@
<F.Options>
{{#each this.supportedAuthTypes as |type|}}
<option selected={{eq this.selectedAuthMethod type}} value={{type}}>
{{auth-display-name type}}
{{get (engines-display-data type) "displayName"}}
</option>
{{/each}}
</F.Options>

View File

@ -5,7 +5,7 @@
<Hds::Tabs @onClickTab={{this.onClickTab}} @selectedTabIndex={{this.selectedTabIndex}} as |T|>
{{#each-in @authTabData as |methodType mounts|}}
<T.Tab data-test-auth-tab={{methodType}}>{{auth-display-name methodType}}</T.Tab>
<T.Tab data-test-auth-tab={{methodType}}>{{get (engines-display-data methodType) "displayName"}}</T.Tab>
<T.Panel>
<div class="has-top-padding-m">
{{! Elements "behind" tabs always render on the DOM and are just superficially hidden/shown.

View File

@ -9,13 +9,13 @@
{{#if this.showEnable}}
{{#let (find-by "type" @mountModel.type @mountTypes) as |typeInfo|}}
<Icon @name={{typeInfo.glyph}} @size="24" class="has-text-grey-light" />
{{#if (eq @mountType "secret")}}
{{#if (eq @mountCategory "secret")}}
{{concat "Enable " typeInfo.displayName " Secrets Engine"}}
{{else}}
{{concat "Enable " typeInfo.displayName " Authentication Method"}}
{{/if}}
{{/let}}
{{else if (eq @mountType "secret")}}
{{else if (eq @mountCategory "secret")}}
Enable a Secrets Engine
{{else}}
Enable an Authentication Method
@ -25,13 +25,13 @@
</PageHeader>
<div class="box is-sideless is-bottomless is-fullwidth is-marginless">
<NamespaceReminder @mode="enable" @noun={{if (eq @mountType "secret") "Secret Engine" "Auth Method"}} />
<NamespaceReminder @mode="enable" @noun={{if (eq @mountCategory "secret") "Secret Engine" "Auth Method"}} />
<MessageError @errorMessage={{this.errorMessage}} />
{{#if @mountModel.type}}
<form {{on "submit" (perform this.mountBackend)}}>
<FormFieldGroups
@model={{@mountModel}}
@groupName={{if (eq @mountType "secret") "formFieldGroups"}}
@groupName={{if (eq @mountCategory "secret") "formFieldGroups"}}
@renderGroup="default"
@modelValidations={{this.modelValidations}}
@onKeyUp={{this.onKeyUp}}
@ -40,7 +40,7 @@
<FormFieldGroups
@model={{@mountModel}}
@renderGroup="Method Options"
@groupName={{if (eq @mountType "secret") "formFieldGroups"}}
@groupName={{if (eq @mountCategory "secret") "formFieldGroups"}}
>
<:identityTokenKey>
<SearchSelectWithModal
@ -61,7 +61,7 @@
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="control">
<Hds::Button
@text={{if (eq @mountType "secret") "Enable engine" "Enable method"}}
@text={{if (eq @mountCategory "secret") "Enable engine" "Enable method"}}
@icon={{if this.mountBackend.isRunning "loading"}}
type="submit"
data-test-submit
@ -80,6 +80,6 @@
</form>
{{else}}
{{! Type not yet set, show type options }}
<MountBackend::TypeForm @setMountType={{this.setMountType}} @mountType={{@mountType}} />
<MountBackend::TypeForm @setMountType={{this.setMountType}} @mountCategory={{@mountCategory}} />
{{/if}}
</div>

View File

@ -9,13 +9,14 @@ import { service } from '@ember/service';
import { action } from '@ember/object';
import { task } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';
import { methods } from 'vault/helpers/mountable-auth-methods';
import { isAddonEngine, allEngines } from 'vault/helpers/mountable-secret-engines';
import { presence } from 'vault/utils/forms/validators';
import { filterEnginesByMountCategory, isAddonEngine } from 'vault/utils/all-engines-metadata';
import { assert } from '@ember/debug';
import { ResponseError } from '@hashicorp/vault-client-typescript';
import AdapterError from '@ember-data/adapter/error';
import type FlashMessageService from 'vault/services/flash-messages';
import type Store from '@ember-data/store';
import type AdapterError from '@ember-data/adapter/error';
import type { AuthEnableModel } from 'vault/routes/vault/cluster/settings/auth/enable';
import type SecretsEngineForm from 'vault/forms/secrets/engine';
import type CapabilitiesService from 'vault/services/capabilities';
@ -27,10 +28,10 @@ import type { ApiError } from '@ember-data/adapter/error';
* The `MountBackendForm` is used to mount either a secret or auth backend.
*
* @example ```js
* <MountBackendForm @mountType="secret" @onMountSuccess={{this.onMountSuccess}} />```
* <MountBackendForm @mountCategory="secret" @onMountSuccess={{this.onMountSuccess}} />```
*
* @param {function} onMountSuccess - A function that transitions once the Mount has been successfully posted.
* @param {string} [mountType=auth] - The type of backend we want to mount.
* @param {string} mountCategory - The type of engine to mount, either 'secret' or 'auth'.
*
*/
@ -38,7 +39,7 @@ type MountModel = SecretsEngineForm | AuthEnableModel;
interface Args {
mountModel: MountModel;
mountType: 'secret' | 'auth';
mountCategory: 'secret' | 'auth';
onMountSuccess: (type: string, path: string, useEngineRoute: boolean) => void;
}
@ -54,41 +55,47 @@ export default class MountBackendForm extends Component<Args> {
@tracked errorMessage: string | string[] = '';
constructor(owner: unknown, args: Args) {
super(owner, args);
assert(`@mountCategory is required. Must be "auth" or "secret".`, presence(this.args.mountCategory));
}
willDestroy() {
// components are torn down after store is unloaded and will cause an error if attempt to unload record
const noTeardown = this.store && !this.store.isDestroying;
if (noTeardown && this.args.mountType === 'auth' && this.args?.mountModel?.isNew) {
if (noTeardown && this.args.mountCategory === 'auth' && this.args?.mountModel?.isNew) {
this.args.mountModel.unloadRecord();
}
super.willDestroy();
}
checkPathChange(type: string) {
if (!type) return;
checkPathChange(backendType: string) {
if (!backendType) return;
const mount = this.args.mountModel;
const currentPath = mount.path;
const mountTypes =
this.args.mountType === 'secret'
? allEngines().map((engine) => engine.type)
: methods().map((auth) => auth.type);
// mountCategory is usually 'secret' or 'auth', but sometimes an empty string is passed in (like when we click the cancel button).
// In these cases, we should default to returning auth methods.
const mountsByType = filterEnginesByMountCategory({
mountCategory: this.args.mountCategory ?? 'auth',
isEnterprise: true,
}).map((engine) => engine.type);
// if the current path has not been altered by user,
// change it here to match the new type
if (!currentPath || mountTypes.includes(currentPath)) {
mount.path = type;
if (!currentPath || mountsByType.includes(currentPath)) {
mount.path = backendType;
}
}
typeChangeSideEffect(type: string) {
if (this.args.mountType === 'secret') {
// If type PKI, set max lease to ~10years
this.args.mountModel.config.maxLeaseTtl = type === 'pki' ? '3650d' : 0;
}
if (this.args.mountCategory !== 'secret') return;
// If type PKI, set max lease to ~10years
this.args.mountModel.config.maxLeaseTtl = type === 'pki' ? '3650d' : 0;
}
checkModelValidity(model: MountModel) {
const { mountType } = this.args;
const { mountCategory } = this.args;
const { isValid, state, invalidFormMessage, data } =
mountType === 'secret' ? model.toJSON() : model.validate();
mountCategory === 'secret' ? model.toJSON() : model.validate();
this.modelValidations = state;
this.invalidFormAlert = invalidFormMessage;
return { isValid, data };
@ -97,8 +104,8 @@ export default class MountBackendForm extends Component<Args> {
checkModelWarnings() {
// check for warnings on change
// since we only show errors on submit we need to clear those out and only send warning state
const { mountType, mountModel } = this.args;
const { state } = mountType === 'secret' ? mountModel.toJSON() : mountModel.validate();
const { mountCategory, mountModel } = this.args;
const { state } = mountCategory === 'secret' ? mountModel.toJSON() : mountModel.validate();
for (const key in state) {
state[key].errors = [];
}
@ -152,7 +159,7 @@ export default class MountBackendForm extends Component<Args> {
@waitFor
*mountBackend(event: Event) {
event.preventDefault();
const { mountModel, mountType } = this.args;
const { mountModel, mountCategory } = this.args;
const { type, path } = mountModel;
// only submit form if validations pass
const { isValid, data: formData } = this.checkModelValidity(mountModel);
@ -161,7 +168,7 @@ export default class MountBackendForm extends Component<Args> {
}
try {
if (mountType === 'secret') {
if (mountCategory === 'secret') {
yield this.api.sys.mountsEnableSecretsEngine(path, formData);
yield this.saveKvConfig(path, formData);
} else {
@ -169,7 +176,7 @@ export default class MountBackendForm extends Component<Args> {
}
this.flashMessages.success(
`Successfully mounted the ${type} ${
this.args.mountType === 'secret' ? 'secrets engine' : 'auth method'
this.args.mountCategory === 'secret' ? 'secrets engine' : 'auth method'
} at ${path}.`
);
// check whether to use the Ember engine route

View File

@ -8,7 +8,7 @@
{{capitalize category}}
</Hds::Text::Display>
<div class="flex row-wrap row-gap-8 column-gap-16 has-bottom-padding-m">
{{#each (filter-by "category" category this.mountTypes) as |type|}}
{{#each (filter-by "pluginCategory" category this.mountTypes) as |type|}}
<SelectableCard
id={{type.type}}
class="has-top-padding-l has-text-centered small-card"
@ -32,7 +32,7 @@
<Hds::Button
@text="Cancel"
@color="secondary"
@route={{if (eq @mountType "secret") "vault.cluster.secrets.backends" "vault.cluster.access.methods"}}
@route={{if (eq @mountCategory "secret") "vault.cluster.secrets.backends" "vault.cluster.access.methods"}}
data-test-cancel
/>
</div>

View File

@ -5,8 +5,7 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { allMethods, methods } from 'vault/helpers/mountable-auth-methods';
import { allEngines, mountableEngines } from 'vault/helpers/mountable-secret-engines';
import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
/**
*
@ -16,24 +15,28 @@ import { allEngines, mountableEngines } from 'vault/helpers/mountable-secret-eng
*
* @example
* ```js
* <MountBackend::TypeForm @setMountType={{this.setMountType}} @mountType="secret" />
* <MountBackend::TypeForm @setMountType={{this.setMountType}} @mountCategory="secret" />
* ```
* @param {CallableFunction} setMountType - function will receive the mount type string. Should update the model type value
* @param {string} [mountType=auth] - mount type can be `auth` or `secret`
* @param {string} [mountCategory=auth] - mount category can be `auth` or `secret`
*/
export default class MountBackendTypeForm extends Component {
@service version;
get secretEngines() {
return this.version.isEnterprise ? allEngines() : mountableEngines();
// If an enterprise license is present, return all secret engines;
// otherwise, return only the secret engines supported in OSS.
return filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: this.version.isEnterprise });
}
get authMethods() {
return this.version.isEnterprise ? allMethods() : methods();
// If an enterprise license is present, return all auth methods;
// otherwise, return only the auth methods supported in OSS.
return filterEnginesByMountCategory({ mountCategory: 'auth', isEnterprise: this.version.isEnterprise });
}
get mountTypes() {
return this.args.mountType === 'secret' ? this.secretEngines : this.authMethods;
return this.args.mountCategory === 'secret' ? this.secretEngines : this.authMethods;
}
}

View File

@ -48,7 +48,21 @@
<div>
<div class="has-text-grey is-grid align-items-center linked-block-title">
{{#if backend.icon}}
<Hds::TooltipButton @text={{or backend.engineType backend.path}} aria-label="Type of backend">
<Hds::TooltipButton
@text={{if
backend.isSupportedBackend
(concat
(get (engines-display-data backend.type) "displayName")
(if
(eq backend.version 2)
" version 2"
(if (and (eq backend.version 1) (eq backend.type "kv")) " version 1" "")
)
)
"The UI only supports configuration views for these secret engines. The CLI must be used to manage other engine resources."
}}
aria-label="Type of backend"
>
<Icon @name={{backend.icon}} class="has-text-grey-light" />
</Hds::TooltipButton>
{{/if}}
@ -67,11 +81,10 @@
{{/if}}
{{/if}}
</div>
{{#if backend.accessor}}
<code class="has-text-grey is-size-8">
{{if (eq backend.version 2) (concat "v2 " backend.accessor) backend.accessor}}
</code>
{{/if}}
<code class="has-text-grey is-size-8" data-test-engine-accessor>
{{backend.accessor}}
{{backend.runningPluginVersion}}
</code>
{{#if backend.description}}
<ReadMore>
{{backend.description}}

View File

@ -29,7 +29,7 @@ interface Args {
secretEngines: Array<SecretsEngineResource>;
}
export default class SecretListItem extends Component<Args> {
export default class SecretEngineList extends Component<Args> {
@service declare readonly flashMessages: FlashMessageService;
@service declare readonly api: ApiService;
@service declare readonly router: RouterService;

View File

@ -4,13 +4,13 @@
*/
import Controller from '@ember/controller';
import { WIF_ENGINES, allEngines } from 'vault/helpers/mountable-secret-engines';
import engineDisplayData from 'vault/helpers/engines-display-data';
export default class SecretsBackendConfigurationEditController extends Controller {
get isWifEngine() {
return WIF_ENGINES.includes(this.model.type);
return engineDisplayData(this.model.type)?.isWIF;
}
get displayName() {
return allEngines().find((engine) => engine.type === this.model.type)?.displayName;
return engineDisplayData(this.model.type).displayName;
}
}

View File

@ -4,8 +4,8 @@
*/
import Controller from '@ember/controller';
import { WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
import { toLabel } from 'core/helpers/to-label';
import engineDisplayData from 'vault/helpers/engines-display-data';
export default class SecretsBackendConfigurationController extends Controller {
get displayFields() {
@ -26,7 +26,7 @@ export default class SecretsBackendConfigurationController extends Controller {
fields.push('version');
}
// For WIF Secret engines, allow users to set the identity token key when mounting the engine.
if (WIF_ENGINES.includes(engineType)) {
if (engineDisplayData(engineType)?.isWIF) {
fields.push('config.identityTokenKey');
}
return fields;

View File

@ -5,9 +5,9 @@
import { service } from '@ember/service';
import Controller from '@ember/controller';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import { allEngines } from 'vault/helpers/mountable-secret-engines';
import { action } from '@ember/object';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import engineDisplayData from 'vault/helpers/engines-display-data';
const SUPPORTED_BACKENDS = supportedSecretBackends();
@ -18,14 +18,15 @@ export default class MountSecretBackendController extends Controller {
onMountSuccess(type, path, useEngineRoute = false) {
let transition;
if (SUPPORTED_BACKENDS.includes(type)) {
const engineInfo = allEngines().find((engine) => engine.type === type);
const engineInfo = engineDisplayData(type);
if (useEngineRoute) {
transition = this.router.transitionTo(
`vault.cluster.secrets.backend.${engineInfo.engineRoute}`,
path
);
} else {
const queryParams = engineInfo?.routeQueryParams || {};
// For keymgmt, we need to land on provider tab by default using query params
const queryParams = engineInfo.type === 'keymgmt' ? { tab: 'provider' } : {};
transition = this.router.transitionTo('vault.cluster.secrets.backend.index', path, { queryParams });
}
} else {

View File

@ -7,8 +7,8 @@ import Form from 'vault/forms/form';
import FormField from 'vault/utils/forms/field';
import FormFieldGroup from 'vault/utils/forms/field-group';
import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
import { WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
import { tracked } from '@glimmer/tracking';
import { ALL_ENGINES } from 'vault/utils/all-engines-metadata';
import type { SecretsEngineFormData } from 'vault/secrets/engine';
import type { Validations } from 'vault/app-types';
@ -134,7 +134,7 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
if (this.engineType === 'pki') {
return [...this.coreOptionFields, ...this.standardConfigFields];
}
if (WIF_ENGINES.find((type) => type === this.engineType)) {
if (ALL_ENGINES.find((engine) => engine.type === this.engineType && engine.isWIF)?.type) {
return [
...this.coreOptionFields,
defaultTtl,

View File

@ -1,11 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { ALL_LOGIN_METHODS } from 'vault/utils/supported-login-methods';
export default function authDisplayName(methodType: string) {
const displayName = ALL_LOGIN_METHODS?.find((t) => t.type === methodType)?.displayName;
return displayName || methodType;
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { ALL_ENGINES } from 'vault/utils/all-engines-metadata';
/**
* Helper function to retrieve engine metadata for a given `methodType`.
* It searches the `ALL_ENGINES` array for an engine with a matching type and returns its metadata object.
* The `ALL_ENGINES` array includes secret and auth engines, including those supported only in enterprise.
* These details (such as mount type and enterprise licensing) are included in the returned engine object.
*
* Example usage:
* const engineMetadata = engineDisplayData('kmip');
* if (engineMetadata?.requiresEnterprise) {
* console.log(`This mount: ${engineMetadata.engineType} requires an enterprise license`);
* }
*
* @param {string} methodType - The engine type (sometimes called backend) to look up (e.g., "aws", "azure").
* @returns {Object|undefined} - The engine metadata, which includes information about its mount type (e.g., secret or auth)
* and whether it requires an enterprise license. Returns undefined if no match is found.
*/
export default function engineDisplayData(methodType: string) {
const engine = ALL_ENGINES?.find((t) => t.type === methodType);
return engine;
}

View File

@ -1,133 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { helper as buildHelper } from '@ember/component/helper';
/**
* These are all the auth methods that can be mounted.
* Some methods may not be available for login via the UI,
* which are in the `supported-auth-backends` helper.
*/
const ENTERPRISE_AUTH_METHODS = [
{
displayName: 'SAML',
value: 'saml',
type: 'saml',
category: 'generic',
glyph: 'saml-color',
},
];
const MOUNTABLE_AUTH_METHODS = [
{
displayName: 'AliCloud',
value: 'alicloud',
type: 'alicloud',
category: 'cloud',
glyph: 'alibaba-color',
},
{
displayName: 'AppRole',
value: 'approle',
type: 'approle',
category: 'generic',
glyph: 'cpu',
},
{
displayName: 'AWS',
value: 'aws',
type: 'aws',
category: 'cloud',
glyph: 'aws-color',
},
{
displayName: 'Azure',
value: 'azure',
type: 'azure',
category: 'cloud',
glyph: 'azure-color',
},
{
displayName: 'Google Cloud',
value: 'gcp',
type: 'gcp',
category: 'cloud',
glyph: 'gcp-color',
},
{
displayName: 'GitHub',
value: 'github',
type: 'github',
category: 'cloud',
glyph: 'github-color',
},
{
displayName: 'JWT',
value: 'jwt',
type: 'jwt',
glyph: 'jwt',
category: 'generic',
},
{
displayName: 'OIDC',
value: 'oidc',
type: 'oidc',
glyph: 'openid-color',
category: 'generic',
},
{
displayName: 'Kubernetes',
value: 'kubernetes',
type: 'kubernetes',
category: 'infra',
glyph: 'kubernetes-color',
},
{
displayName: 'LDAP',
value: 'ldap',
type: 'ldap',
glyph: 'folder-users',
category: 'infra',
},
{
displayName: 'Okta',
value: 'okta',
type: 'okta',
category: 'infra',
glyph: 'okta-color',
},
{
displayName: 'RADIUS',
value: 'radius',
type: 'radius',
glyph: 'mainframe',
category: 'infra',
},
{
displayName: 'TLS Certificates',
value: 'cert',
type: 'cert',
category: 'generic',
glyph: 'certificate',
},
{
displayName: 'Username & Password',
value: 'userpass',
type: 'userpass',
category: 'generic',
glyph: 'users',
},
];
export function methods() {
return MOUNTABLE_AUTH_METHODS.slice();
}
export function allMethods() {
return [...MOUNTABLE_AUTH_METHODS, ...ENTERPRISE_AUTH_METHODS];
}
export default buildHelper(methods);

View File

@ -1,179 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { helper as buildHelper } from '@ember/component/helper';
const ENTERPRISE_SECRET_ENGINES = [
{
displayName: 'KMIP',
type: 'kmip',
glyph: 'lock',
engineRoute: 'kmip.scopes.index',
category: 'generic',
requiredFeature: 'KMIP',
},
{
displayName: 'Transform',
type: 'transform',
category: 'generic',
requiredFeature: 'Transform Secrets Engine',
glyph: 'transform-data',
},
{
displayName: 'Key Management',
type: 'keymgmt',
glyph: 'key',
category: 'cloud',
requiredFeature: 'Key Management Secrets Engine',
routeQueryParams: { tab: 'provider' },
},
];
const MOUNTABLE_SECRET_ENGINES = [
{
displayName: 'AliCloud',
type: 'alicloud',
glyph: 'alibaba-color',
category: 'cloud',
},
{
displayName: 'AWS',
type: 'aws',
category: 'cloud',
glyph: 'aws-color',
},
{
displayName: 'Azure',
type: 'azure',
category: 'cloud',
glyph: 'azure-color',
},
{
displayName: 'Consul',
type: 'consul',
glyph: 'consul-color',
category: 'infra',
},
{
displayName: 'Databases',
type: 'database',
category: 'infra',
glyph: 'database',
},
{
displayName: 'Google Cloud',
type: 'gcp',
category: 'cloud',
glyph: 'gcp-color',
},
{
displayName: 'Google Cloud KMS',
type: 'gcpkms',
category: 'cloud',
glyph: 'gcp-color',
},
{
displayName: 'KV',
type: 'kv',
glyph: 'key-values',
engineRoute: 'kv.list',
category: 'generic',
},
{
displayName: 'Nomad',
type: 'nomad',
glyph: 'nomad-color',
category: 'infra',
},
{
displayName: 'PKI Certificates',
type: 'pki',
glyph: 'certificate',
engineRoute: 'pki.overview',
category: 'generic',
},
{
displayName: 'RabbitMQ',
type: 'rabbitmq',
glyph: 'rabbitmq-color',
category: 'infra',
},
{
displayName: 'SSH',
type: 'ssh',
glyph: 'terminal-screen',
category: 'generic',
},
{
displayName: 'Transit',
type: 'transit',
glyph: 'swap-horizontal',
category: 'generic',
},
{
displayName: 'TOTP',
type: 'totp',
glyph: 'history',
category: 'generic',
},
{
displayName: 'LDAP',
type: 'ldap',
engineRoute: 'ldap.overview',
category: 'generic',
glyph: 'folder-users',
},
{
displayName: 'Kubernetes',
type: 'kubernetes',
engineRoute: 'kubernetes.overview',
category: 'generic',
glyph: 'kubernetes-color',
},
];
// A list of Workload Identity Federation engines.
export const WIF_ENGINES = ['aws', 'azure', 'gcp'];
export function wifEngines() {
return WIF_ENGINES.slice();
}
// The UI only supports configuration views for these secrets engines. The CLI must be used to manage other engine resources (i.e. roles, credentials).
export const CONFIGURATION_ONLY = ['azure', 'gcp'];
export function configurationOnly() {
return CONFIGURATION_ONLY.slice();
}
// 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', 'gcp', 'ssh'];
export function configurableSecretEngines() {
return CONFIGURABLE_SECRET_ENGINES.slice();
}
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', 'gcpkms', 'nomad', 'rabbitmq'];
export function unsupportedEngines() {
return UNSUPPORTED_ENGINES.slice();
}
export function allEngines() {
return [...MOUNTABLE_SECRET_ENGINES, ...ENTERPRISE_SECRET_ENGINES];
}
export function isAddonEngine(type, version) {
if (type === 'kv' && version === 1) return false;
const engineRoute = allEngines().find((engine) => engine.type === type)?.engineRoute;
return !!engineRoute;
}
export default buildHelper(mountableEngines);

View File

@ -7,8 +7,6 @@ import { helper as buildHelper } from '@ember/component/helper';
/**
* These are all the auth methods with which a user can log into the UI.
* This is a subset of the methods found in the `mountable-auth-methods` helper,
* which lists all the methods that can be mounted.
*/
const SUPPORTED_AUTH_BACKENDS = [

View File

@ -8,12 +8,12 @@ import { service } from '@ember/service';
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
import apiPath from 'vault/utils/api-path';
import { withModelValidations } from 'vault/decorators/model-validations';
import { allMethods } from 'vault/helpers/mountable-auth-methods';
import lazyCapabilities from 'vault/macros/lazy-capabilities';
import { action } from '@ember/object';
import { camelize } from '@ember/string';
import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
import { supportedTypes } from 'vault/utils/supported-login-methods';
import engineDisplayData from 'vault/helpers/engines-display-data';
const validations = {
path: [
@ -45,9 +45,9 @@ export default class AuthMethodModel extends Model {
}
get icon() {
const authMethods = allMethods().find((backend) => backend.type === this.methodType);
return authMethods?.glyph || 'users';
// methodType refers to the backend type (e.g., "aws", "azure") and is set on a getter.
const engineData = engineDisplayData(this.methodType);
return engineData?.glyph || 'users';
}
get directLoginLink() {

View File

@ -6,11 +6,11 @@
import Model, { attr, hasMany } from '@ember-data/model';
import ArrayProxy from '@ember/array/proxy';
import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
import { methods } from 'vault/helpers/mountable-auth-methods';
import { withModelValidations } from 'vault/decorators/model-validations';
import { isPresent } from '@ember/utils';
import { service } from '@ember/service';
import { addManyToArray, addToArray } from 'vault/helpers/add-to-array';
import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
const validations = {
name: [{ type: 'presence', message: 'Name is required' }],
@ -109,7 +109,7 @@ export default class MfaLoginEnforcementModel extends Model {
}
iconForMount(type) {
const mountableMethods = methods();
const mountableMethods = filterEnginesByMountCategory({ mountCategory: 'auth', isEnterprise: true });
const mount = mountableMethods.find((method) => method.type === type);
return mount ? mount.glyph || mount.type : 'token';
}

View File

@ -9,8 +9,9 @@ import { equal } from '@ember/object/computed'; // eslint-disable-line
import { withModelValidations } from 'vault/decorators/model-validations';
import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import { isAddonEngine, allEngines, WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
import { ALL_ENGINES, isAddonEngine } from 'vault/utils/all-engines-metadata';
import engineDisplayData from 'vault/helpers/engines-display-data';
const LINKED_BACKENDS = supportedSecretBackends();
@ -99,6 +100,14 @@ export default class SecretEngineModel extends Model {
})
deleteVersionAfter;
// `plugin_version` represents the version specified at mount time (if any), and is only used for external plugins.
// For built-in plugins, this field is intentionally left empty to simplify upgrades.
//
// `running_plugin_version` reflects the actual version of the plugin currently running,
// regardless of whether it is built-in or external. This provides a reliable source of truth
// and is why we are surfacing it over plugin_version.
@attr('string') runningPluginVersion;
/* GETTERS */
get isV2KV() {
return this.version === 2 && (this.engineType === 'kv' || this.engineType === 'generic');
@ -115,7 +124,7 @@ export default class SecretEngineModel extends Model {
}
get icon() {
const engineData = allEngines().find((engine) => engine.type === this.engineType);
const engineData = engineDisplayData(this.engineType);
return engineData?.glyph || 'lock';
}
@ -137,8 +146,7 @@ export default class SecretEngineModel extends Model {
return 'vault.cluster.secrets.backend.overview';
}
if (isAddonEngine(this.engineType, this.version)) {
const { engineRoute } = allEngines().find((engine) => engine.type === this.engineType);
return `vault.cluster.secrets.backend.${engineRoute}`;
return `vault.cluster.secrets.backend.${engineDisplayData(this.engineType).engineRoute}`;
}
if (this.isV2KV) {
// if it's KV v2 but not registered as an addon, it's type generic
@ -160,7 +168,7 @@ export default class SecretEngineModel extends Model {
get formFields() {
const type = this.engineType;
const fields = ['type', 'path', 'description', 'accessor', 'local', 'sealWrap'];
const fields = ['type', 'path', 'description', 'accessor', 'runningPluginVersion', 'local', 'sealWrap'];
// no ttl options for keymgmt
if (type !== 'keymgmt') {
fields.push('config.defaultLeaseTtl', 'config.maxLeaseTtl');
@ -180,7 +188,7 @@ export default class SecretEngineModel extends Model {
fields.push('casRequired', 'deleteVersionAfter', 'maxVersions');
}
// For WIF Secret engines, allow users to set the identity token key when mounting the engine.
if (WIF_ENGINES.includes(type)) {
if (engineDisplayData(type)?.isWIF) {
fields.push('config.identityTokenKey');
}
return fields;
@ -232,7 +240,7 @@ export default class SecretEngineModel extends Model {
// no ttl options for keymgmt
optionFields = [...CORE_OPTIONS, 'config.allowedManagedKeys', ...STANDARD_CONFIG];
break;
case WIF_ENGINES.find((type) => type === this.engineType):
case ALL_ENGINES.find((engine) => engine.type === this.engineType && engine.isWIF)?.type:
defaultFields = ['path'];
optionFields = [
...CORE_OPTIONS,

View File

@ -4,8 +4,9 @@
*/
import { baseResourceFactory } from 'vault/resources/base-factory';
import { isAddonEngine, allEngines } from 'vault/helpers/mountable-secret-engines';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import { isAddonEngine } from 'vault/utils/all-engines-metadata';
import engineDisplayData from 'vault/helpers/engines-display-data';
import type { SecretsEngine } from 'vault/secrets/engine';
@ -30,7 +31,7 @@ export default class SecretsEngineResource extends baseResourceFactory<SecretsEn
}
get icon() {
const engineData = allEngines().find((engine) => engine.type === this.engineType);
const engineData = engineDisplayData(this.engineType);
return engineData?.glyph || 'lock';
}
@ -52,7 +53,7 @@ export default class SecretsEngineResource extends baseResourceFactory<SecretsEn
return 'vault.cluster.secrets.backend.overview';
}
if (isAddonEngine(this.engineType, this.version)) {
const engine = allEngines().find((engine) => engine.type === this.engineType);
const engine = engineDisplayData(this.engineType);
if (engine?.engineRoute) {
return `vault.cluster.secrets.backend.${engine.engineRoute}`;
}

View File

@ -5,11 +5,11 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { CONFIGURABLE_SECRET_ENGINES } from 'vault/helpers/mountable-secret-engines';
import AwsConfigForm from 'vault/forms/secrets/aws-config';
import AzureConfigForm from 'vault/forms/secrets/azure-config';
import GcpConfigForm from 'vault/forms/secrets/gcp-config';
import SshConfigForm from 'vault/forms/secrets/ssh-config';
import engineDisplayData from 'vault/helpers/engines-display-data';
import type SecretsEngineResource from 'vault/resources/secrets/engine';
import type ApiService from 'vault/services/api';
@ -47,7 +47,7 @@ export default class SecretsBackendConfigurationEdit extends Route {
}[type] || { issuer: '' };
// if the engine type is not configurable or a form class does not exist for the type return a 404.
if (!CONFIGURABLE_SECRET_ENGINES.includes(type) || !formClass) {
if (!engineDisplayData(type)?.isConfigurable || !formClass) {
throw { httpStatus: 404, backend };
}

View File

@ -4,15 +4,14 @@
*/
import Route from '@ember/routing/route';
import { CONFIGURABLE_SECRET_ENGINES, allEngines } from 'vault/helpers/mountable-secret-engines';
import engineDisplayData from 'vault/helpers/engines-display-data';
export default class SecretsBackendConfigurationIndexRoute extends Route {
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
controller.typeDisplay = allEngines().find(
(engine) => engine.type === resolvedModel.secretsEngine.type
)?.displayName;
controller.isConfigurable = CONFIGURABLE_SECRET_ENGINES.includes(resolvedModel.secretsEngine.type);
const engine = engineDisplayData(resolvedModel.secretsEngine.type);
controller.typeDisplay = engine.displayName;
controller.isConfigurable = engine.isConfigurable ?? false;
controller.modelId = resolvedModel.secretsEngine.id;
}
}

View File

@ -7,11 +7,12 @@ import { set } from '@ember/object';
import { hash } from 'rsvp';
import Route from '@ember/routing/route';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import { allEngines, isAddonEngine, CONFIGURATION_ONLY } from 'vault/helpers/mountable-secret-engines';
import { isAddonEngine, filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import { service } from '@ember/service';
import { normalizePath } from 'vault/utils/path-encoding-helpers';
import { assert } from '@ember/debug';
import { pathIsDirectory } from 'kv/utils/kv-breadcrumbs';
import engineDisplayData from 'vault/helpers/engines-display-data';
const SUPPORTED_BACKENDS = supportedSecretBackends();
@ -85,11 +86,13 @@ export default Route.extend({
const type = secretEngine?.engineType;
assert('secretEngine.engineType is not defined', !!type);
// if configuration only, redirect to configuration route
if (CONFIGURATION_ONLY.includes(type)) {
if (engineDisplayData(type)?.isOnlyMountable) {
return this.router.transitionTo('vault.cluster.secrets.backend.configuration', backend);
}
const engineRoute = allEngines().find((engine) => engine.type === type)?.engineRoute;
const engineRoute = filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: true }).find(
(engine) => engine.type === type
)?.engineRoute;
if (!type || !SUPPORTED_BACKENDS.includes(type)) {
return this.router.transitionTo('vault.cluster.secrets');
}

View File

@ -5,7 +5,7 @@
import ApplicationSerializer from './application';
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
import { WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
import engineDisplayData from 'vault/helpers/engines-display-data';
export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
@ -85,7 +85,7 @@ export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
data.options = data.version ? { version: data.version } : {};
delete data.version;
if (!WIF_ENGINES.includes(type)) {
if (!engineDisplayData(type)?.isWIF) {
// only send identity_token_key if it's set on a WIF secret engine.
// because of issues with the model unloading with a belongsTo relationships
// identity_token_key can accidentally carry over if a user backs out of the form and changes the type from WIF to non-WIF.

View File

@ -14,7 +14,7 @@
<p.levelLeft>
<h1 class="title is-3">
Configure
{{get (find-by "type" this.model.type (mountable-auth-methods)) "displayName"}}
{{get (engines-display-data this.model.type) "displayName"}}
</h1>
</p.levelLeft>
</PageHeader>

View File

@ -3,4 +3,4 @@
SPDX-License-Identifier: BUSL-1.1
}}
<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{action "onMountSuccess"}} />
<MountBackendForm @mountModel={{this.model}} @mountCategory="auth" @onMountSuccess={{action "onMountSuccess"}} />

View File

@ -3,4 +3,4 @@
SPDX-License-Identifier: BUSL-1.1
}}
<MountBackendForm @mountModel={{this.model}} @mountType="secret" @onMountSuccess={{action "onMountSuccess"}} />
<MountBackendForm @mountModel={{this.model}} @mountCategory="secret" @onMountSuccess={{action "onMountSuccess"}} />

View File

@ -0,0 +1,301 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
/**
* Metadata configuration for secret and auth engines, including enterprise.
*
* This file defines and exports engine metadata, including its
* displayName, mountCategory, requiresEnterprise, and other relevant properties. It serves as a
* centralized source of truth for engine-related configurations.
*
* Key responsibilities:
* - Define metadata for all engines.
* - Provide utility functions or constants for accessing engine-specific data.
* - Facilitate dynamic engine rendering and behavior based on metadata.
*
* Example usage:
* // If an enterprise license is present, return all secret engines;
* // otherwise, return only the secret engines supported in OSS.
* return filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: this.version.isEnterprise });
*/
export interface EngineDisplayData {
pluginCategory?: string; // The plugin category is used to group engines in the UI. e.g., 'cloud', 'infra', 'generic'
displayName: string;
engineRoute?: string;
glyph?: string;
isWIF?: boolean; // flag for 'Workload Identity Federation' engines.
mountCategory: string[];
requiredFeature?: string; // flag for engines that require the ADP (Advanced Data Protection) feature. - https://www.hashicorp.com/en/blog/advanced-data-protection-adp-now-available-in-hcp-vault
requiresEnterprise?: boolean;
isConfigurable?: boolean; // for secret engines that have their own configuration page and actions. - These engines do not exist in their own Ember engine.
isOnlyMountable?: boolean; // The UI only supports configuration views for these secrets engines. The CLI must be used to manage other engine resources (i.e. roles, credentials).
type: string;
value?: string;
}
/**
* @param mountCategory - Given mount category to filter by, e.g., 'auth' or 'secret'.
* @param isEnterprise - Optional boolean to indicate if enterprise engines should be included in the results.
* @returns Filtered array of engines that match the given mount category
*/
export function filterEnginesByMountCategory({
mountCategory,
isEnterprise = false,
}: {
mountCategory: 'auth' | 'secret';
isEnterprise: boolean;
}) {
return isEnterprise
? ALL_ENGINES.filter((engine) => engine.mountCategory.includes(mountCategory))
: ALL_ENGINES.filter(
(engine) => engine.mountCategory.includes(mountCategory) && !engine.requiresEnterprise
);
}
export function isAddonEngine(type: string, version: number) {
if (type === 'kv' && version === 1) return false;
const engineRoute = ALL_ENGINES.find((engine) => engine.type === type)?.engineRoute;
return !!engineRoute;
}
export const ALL_ENGINES: EngineDisplayData[] = [
{
pluginCategory: 'cloud',
displayName: 'AliCloud',
glyph: 'alibaba-color',
mountCategory: ['auth', 'secret'],
type: 'alicloud',
},
{
pluginCategory: 'generic',
displayName: 'AppRole',
glyph: 'cpu',
mountCategory: ['auth'],
type: 'approle',
value: 'approle',
},
{
pluginCategory: 'cloud',
displayName: 'AWS',
glyph: 'aws-color',
isConfigurable: true,
isWIF: true,
mountCategory: ['auth', 'secret'],
type: 'aws',
},
{
pluginCategory: 'cloud',
displayName: 'Azure',
glyph: 'azure-color',
isOnlyMountable: true,
isConfigurable: true,
isWIF: true,
mountCategory: ['auth', 'secret'],
type: 'azure',
},
{
pluginCategory: 'infra',
displayName: 'Consul',
glyph: 'consul-color',
mountCategory: ['secret'],
type: 'consul',
},
{
displayName: 'Cubbyhole',
type: 'cubbyhole',
mountCategory: ['secret'],
},
{
pluginCategory: 'infra',
displayName: 'Databases',
glyph: 'database',
mountCategory: ['secret'],
type: 'database',
},
{
pluginCategory: 'cloud',
displayName: 'GitHub',
glyph: 'github-color',
mountCategory: ['auth'],
type: 'github',
value: 'github',
},
{
pluginCategory: 'cloud',
displayName: 'Google Cloud',
glyph: 'gcp-color',
isOnlyMountable: true,
isConfigurable: true,
isWIF: true,
mountCategory: ['auth', 'secret'],
type: 'gcp',
},
{
pluginCategory: 'cloud',
displayName: 'Google Cloud KMS',
glyph: 'gcp-color',
mountCategory: ['secret'],
type: 'gcpkms',
},
{
pluginCategory: 'generic',
displayName: 'JWT',
glyph: 'jwt',
mountCategory: ['auth'],
type: 'jwt',
value: 'jwt',
},
{
pluginCategory: 'generic',
displayName: 'KV',
engineRoute: 'kv.list',
glyph: 'key-values',
mountCategory: ['secret'],
type: 'kv',
},
{
pluginCategory: 'generic',
displayName: 'KMIP',
engineRoute: 'kmip.scopes.index',
glyph: 'lock',
mountCategory: ['secret'],
requiredFeature: 'KMIP',
requiresEnterprise: true,
type: 'kmip',
},
{
pluginCategory: 'generic',
displayName: 'Transform',
glyph: 'transform-data',
mountCategory: ['secret'],
requiredFeature: 'Transform Secrets Engine',
requiresEnterprise: true,
type: 'transform',
},
{
pluginCategory: 'cloud',
displayName: 'Key Management',
glyph: 'key',
mountCategory: ['secret'],
requiredFeature: 'Key Management Secrets Engine',
requiresEnterprise: true,
type: 'keymgmt',
},
{
pluginCategory: 'generic',
displayName: 'Kubernetes',
engineRoute: 'kubernetes.overview',
glyph: 'kubernetes-color',
mountCategory: ['auth', 'secret'],
type: 'kubernetes',
},
{
pluginCategory: 'generic',
displayName: 'LDAP',
engineRoute: 'ldap.overview',
glyph: 'folder-users',
mountCategory: ['auth', 'secret'],
type: 'ldap',
},
{
pluginCategory: 'infra',
displayName: 'Nomad',
glyph: 'nomad-color',
mountCategory: ['secret'],
type: 'nomad',
},
{
pluginCategory: 'generic',
displayName: 'OIDC',
glyph: 'openid-color',
mountCategory: ['auth'],
type: 'oidc',
value: 'oidc',
},
{
pluginCategory: 'infra',
displayName: 'Okta',
glyph: 'okta-color',
mountCategory: ['auth'],
type: 'okta',
value: 'okta',
},
{
pluginCategory: 'generic',
displayName: 'PKI Certificates',
engineRoute: 'pki.overview',
glyph: 'certificate',
mountCategory: ['secret'],
type: 'pki',
},
{
pluginCategory: 'infra',
displayName: 'RADIUS',
glyph: 'mainframe',
mountCategory: ['auth'],
type: 'radius',
value: 'radius',
},
{
pluginCategory: 'infra',
displayName: 'RabbitMQ',
glyph: 'rabbitmq-color',
mountCategory: ['secret'],
type: 'rabbitmq',
},
{
pluginCategory: 'generic',
displayName: 'SAML',
glyph: 'saml-color',
mountCategory: ['auth'],
requiresEnterprise: true,
type: 'saml',
value: 'saml',
},
{
pluginCategory: 'generic',
displayName: 'SSH',
glyph: 'terminal-screen',
isConfigurable: true,
mountCategory: ['secret'],
type: 'ssh',
},
{
pluginCategory: 'generic',
displayName: 'TLS Certificates',
glyph: 'certificate',
mountCategory: ['auth'],
type: 'cert',
value: 'cert',
},
{
pluginCategory: 'generic',
displayName: 'TOTP',
glyph: 'history',
mountCategory: ['secret'],
type: 'totp',
},
{
pluginCategory: 'generic',
displayName: 'Transit',
glyph: 'swap-horizontal',
mountCategory: ['secret'],
type: 'transit',
},
{
displayName: 'Token',
type: 'token',
mountCategory: ['auth'],
},
{
pluginCategory: 'generic',
displayName: 'Userpass',
glyph: 'users',
mountCategory: ['auth'],
type: 'userpass',
value: 'userpass',
},
];

View File

@ -4,8 +4,8 @@
*/
import Component from '@glimmer/component';
import engineDisplayData from 'vault/helpers/engines-display-data';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import { CONFIGURATION_ONLY } from 'vault/helpers/mountable-secret-engines';
/**
* @module SecretListHeader
@ -29,6 +29,6 @@ export default class SecretListHeader extends Component {
get showListTab() {
// only show the list tab if the engine is not a configuration only engine and the UI supports it
const { engineType } = this.args.model;
return supportedSecretBackends().includes(engineType) && !CONFIGURATION_ONLY.includes(engineType);
return supportedSecretBackends().includes(engineType) && !engineDisplayData(engineType)?.isOnlyMountable;
}
}

View File

@ -33,14 +33,14 @@ export default class SecretsEngineMountConfig extends Component<Args> {
get fields(): Array<Field> {
const { secretsEngine } = this.args;
return [
{ label: 'Secret Engine Type', value: secretsEngine.engineType },
{ label: 'Secret engine type', value: secretsEngine.engineType },
{ label: 'Path', value: secretsEngine.path },
{ label: 'Accessor', value: secretsEngine.accessor },
{ label: 'Local', value: secretsEngine.local },
{ label: 'Seal Wrap', value: secretsEngine.sealWrap },
{ label: 'Seal wrap', value: secretsEngine.sealWrap },
{ label: 'Default Lease TTL', value: duration([secretsEngine.config.defaultLeaseTtl]) },
{ label: 'Max Lease TTL', value: duration([secretsEngine.config.maxLeaseTtl]) },
{ label: 'Identity Token Key', value: secretsEngine.config.identityTokenKey },
{ label: 'Identity token key', value: secretsEngine.config.identityTokenKey },
];
}
}

View File

@ -11,9 +11,9 @@ import { v4 as uuidv4 } from 'uuid';
import { login, loginNs } from 'vault/tests/helpers/auth/auth-helpers';
import { MANAGED_AUTH_BACKENDS } from 'vault/helpers/supported-managed-auth-backends';
import { deleteAuthCmd, mountAuthCmd, runCmd, createNS } from 'vault/tests/helpers/commands';
import { methods } from 'vault/helpers/mountable-auth-methods';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { MOUNT_BACKEND_FORM } from 'vault/tests/helpers/components/mount-backend-form-selectors';
import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
const SELECTORS = {
createUser: '[data-test-entity-create-link="user"]',
@ -78,7 +78,7 @@ module('Acceptance | auth backend list', function (hooks) {
});
// Test all auth methods, not just those you can log in with
methods()
filterEnginesByMountCategory({ mountCategory: 'auth', isEnterprise: false })
.map((backend) => backend.type)
.forEach((type) => {
test(`${type} auth method`, async function (assert) {

View File

@ -23,7 +23,7 @@ import credentialsPage from 'vault/tests/pages/secrets/backend/kmip/credentials'
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
import { allEngines } from 'vault/helpers/mountable-secret-engines';
import engineDisplayData from 'vault/helpers/engines-display-data';
import { mountEngineCmd, runCmd } from 'vault/tests/helpers/commands';
import { v4 as uuidv4 } from 'uuid';
@ -94,7 +94,7 @@ module('Acceptance | Enterprise | KMIP secrets', function (hooks) {
test('it should enable KMIP & transitions to addon engine route after mount success', async function (assert) {
// test supported backends that ARE ember engines (enterprise only engines are tested individually)
const engine = allEngines().find((e) => e.type === 'kmip');
const engine = engineDisplayData('kmip');
await mountSecrets.visit();
await mountBackend(engine.type, `${engine.type}-${uuidv4()}`);

View File

@ -9,10 +9,10 @@ import { click, currentRouteName, fillIn } from '@ember/test-helpers';
import { login } from 'vault/tests/helpers/auth/auth-helpers';
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { allEngines } from 'vault/helpers/mountable-secret-engines';
import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
import { runCmd } from '../helpers/commands';
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
import engineDisplayData from 'vault/helpers/engines-display-data';
module('Acceptance | Enterprise | keymgmt', function (hooks) {
setupApplicationTest(hooks);
@ -24,7 +24,7 @@ module('Acceptance | Enterprise | keymgmt', function (hooks) {
test('it transitions to list route after mount success', async function (assert) {
assert.expect(1);
const engine = allEngines().find((e) => e.type === 'keymgmt');
const engine = engineDisplayData('keymgmt');
// delete any previous mount with same name
await runCmd([`delete sys/mounts/${engine.type}`]);

View File

@ -17,11 +17,11 @@ import rolesPage from 'vault/tests/pages/secrets/backend/transform/roles';
import alphabetsPage from 'vault/tests/pages/secrets/backend/transform/alphabets';
import searchSelect from 'vault/tests/pages/components/search-select';
import { runCmd } from '../helpers/commands';
import { allEngines } from 'vault/helpers/mountable-secret-engines';
import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
import { v4 as uuidv4 } from 'uuid';
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import engineDisplayData from 'vault/helpers/engines-display-data';
const searchSelectComponent = create(searchSelect);
@ -64,7 +64,7 @@ module('Acceptance | Enterprise | Transform secrets', function (hooks) {
test('it transitions to list route after mount success', async function (assert) {
assert.expect(1);
const engine = allEngines().find((e) => e.type === 'transform');
const engine = engineDisplayData('transform');
// delete any previous mount with same name
await runCmd([`delete sys/mounts/${engine.type}`]);

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import { click, fillIn, currentRouteName, visit, currentURL } from '@ember/test-helpers';
import { click, fillIn, currentRouteName, visit, currentURL, triggerEvent } from '@ember/test-helpers';
import { selectChoose } from 'ember-power-select/test-support';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
@ -62,6 +62,44 @@ module('Acceptance | secret-engine list view', function (hooks) {
await runCmd(deleteEngineCmd('aws'));
});
test('hovering over the icon of an unsupported engine shows unsupported tooltip', async function (assert) {
await visit('/vault/secrets');
await page.enableEngine();
await click(MOUNT_BACKEND_FORM.mountType('nomad'));
await click(GENERAL.submitButton);
await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-type'), 'nomad');
await triggerEvent('.hds-tooltip-button', 'mouseenter');
assert
.dom('.hds-tooltip-container')
.hasText(
'The UI only supports configuration views for these secret engines. The CLI must be used to manage other engine resources.',
'shows tooltip text for unsupported engine'
);
// cleanup
await runCmd(deleteEngineCmd('nomad'));
});
test('hovering over the icon of a supported engine shows engine name and version (if applicable)', async function (assert) {
await visit('/vault/secrets');
await page.enableEngine();
await click(MOUNT_BACKEND_FORM.mountType('ssh'));
await click(GENERAL.submitButton);
await click(GENERAL.breadcrumbLink('Secrets'));
await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-type'), 'kv');
await triggerEvent('.hds-tooltip-button', 'mouseenter');
assert.dom('.hds-tooltip-container').hasText('KV version 2', 'shows tooltip for kv version 2');
await click('[data-test-selected-list-button="delete"]');
await selectChoose(GENERAL.searchSelect.trigger('filter-by-engine-type'), 'ssh');
await triggerEvent('.hds-tooltip-button', 'mouseenter');
assert.dom('.hds-tooltip-container').hasText('SSH', 'shows tooltip for SSH without version');
});
test('enterprise: cannot view list without permissions inside namespace', async function (assert) {
this.version = 'enterprise';
this.backend = `bk-${this.uid}`;

View File

@ -25,7 +25,6 @@ import configPage from 'vault/tests/pages/secrets/backend/configuration';
import { login } from 'vault/tests/helpers/auth/auth-helpers';
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import { CONFIGURATION_ONLY, mountableEngines } from 'vault/helpers/mountable-secret-engines';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
@ -33,6 +32,8 @@ import { MOUNT_BACKEND_FORM } from 'vault/tests/helpers/components/mount-backend
import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
import { SELECTORS as OIDC } from 'vault/tests/helpers/oidc-config';
import { adminOidcCreateRead, adminOidcCreate } from 'vault/tests/helpers/secret-engine/policy-generator';
import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import engineDisplayData from 'vault/helpers/engines-display-data';
const consoleComponent = create(consoleClass);
@ -203,7 +204,9 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
test('it should transition to mountable addon engine after mount success', async function (assert) {
// test supported backends that ARE ember engines (enterprise only engines are tested individually)
const addons = mountableEngines().filter((e) => BACKENDS_WITH_ENGINES.includes(e.type));
const addons = filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: false }).filter(
(e) => BACKENDS_WITH_ENGINES.includes(e.type)
);
assert.expect(addons.length);
for (const engine of addons) {
@ -230,7 +233,9 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
// test supported backends that are not ember engines (enterprise only engines are tested individually)
const nonEngineBackends = supportedSecretBackends().filter((b) => !BACKENDS_WITH_ENGINES.includes(b));
// add back kv because we want to test v1
const engines = mountableEngines().filter((e) => nonEngineBackends.includes(e.type) || e.type === 'kv');
const engines = filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: false }).filter(
(e) => (nonEngineBackends.includes(e.type) || e.type === 'kv') && e.type !== 'cubbyhole'
);
assert.expect(engines.length);
for (const engine of engines) {
@ -247,7 +252,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
}
await click(GENERAL.submitButton);
const route = CONFIGURATION_ONLY.includes(engine.type) ? 'configuration.index' : 'list-root';
const route = engineDisplayData(engine.type)?.isOnlyMountable ? 'configuration.index' : 'list-root';
assert.strictEqual(
currentRouteName(),
`vault.cluster.secrets.backend.${route}`,
@ -262,7 +267,9 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
});
test('it should transition back to backend list for unsupported backends', async function (assert) {
const unsupported = mountableEngines().filter((e) => !supportedSecretBackends().includes(e.type));
const unsupported = filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: false }).filter(
(e) => !supportedSecretBackends().includes(e.type)
);
assert.expect(unsupported.length);
for (const engine of unsupported) {
@ -375,7 +382,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
await visit(`/vault/secrets/${path}/configuration`);
await click(SES.configurationToggle);
assert
.dom(GENERAL.infoRowValue('Identity Token Key'))
.dom(GENERAL.infoRowValue('Identity token key'))
.hasText(newKey, `shows identity token key on configuration page for engine: ${engine}`);
// cleanup
@ -411,7 +418,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
await click(SES.configurationToggle);
assert
.dom(GENERAL.infoRowValue('Identity Token Key'))
.dom(GENERAL.infoRowValue('Identity token key'))
.hasText('general-key', `shows identity token key on configuration page for engine: ${engine}`);
// cleanup

View File

@ -9,10 +9,10 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import { click, fillIn, render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { methods } from 'vault/helpers/mountable-auth-methods';
import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
const userLockoutSupported = ['approle', 'ldap', 'userpass'];
const userLockoutUnsupported = methods()
const userLockoutUnsupported = filterEnginesByMountCategory({ mountCategory: 'auth', isEnterprise: false })
.map((m) => m.type)
.filter((m) => !userLockoutSupported.includes(m));

View File

@ -12,12 +12,14 @@ import { allowAllCapabilitiesStub, noopStub } from 'vault/tests/helpers/stubs';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { MOUNT_BACKEND_FORM } from 'vault/tests/helpers/components/mount-backend-form-selectors';
import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
import { methods } from 'vault/helpers/mountable-auth-methods';
import { mountableEngines, WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
import { ALL_ENGINES, filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
import SecretsEngineForm from 'vault/forms/secrets/engine';
const WIF_ENGINES = ALL_ENGINES.filter((e) => e.isWIF).map((e) => e.type);
module('Integration | Component | mount backend form', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
@ -46,13 +48,16 @@ module('Integration | Component | mount backend form', function (hooks) {
test('it renders default state', async function (assert) {
assert.expect(15);
await render(
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="auth" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
assert
.dom(GENERAL.title)
.hasText('Enable an Authentication Method', 'renders auth header in default state');
for (const method of methods()) {
for (const method of filterEnginesByMountCategory({
mountCategory: 'auth',
isEnterprise: false,
}).filter((engine) => engine.type !== 'token')) {
assert
.dom(MOUNT_BACKEND_FORM.mountType(method.type))
.hasText(method.displayName, `renders type:${method.displayName} picker`);
@ -61,7 +66,7 @@ module('Integration | Component | mount backend form', function (hooks) {
test('it changes path when type is changed', async function (assert) {
await render(
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="auth" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
await click(MOUNT_BACKEND_FORM.mountType('aws'));
@ -73,7 +78,7 @@ module('Integration | Component | mount backend form', function (hooks) {
test('it keeps path value if the user has changed it', async function (assert) {
await render(
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="auth" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
await click(MOUNT_BACKEND_FORM.mountType('approle'));
assert.strictEqual(this.model.type, 'approle', 'Updates type on model');
@ -90,7 +95,7 @@ module('Integration | Component | mount backend form', function (hooks) {
test('it does not show a selected token type when first mounting an auth method', async function (assert) {
await render(
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="auth" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
await click(MOUNT_BACKEND_FORM.mountType('github'));
await click(GENERAL.button('Method Options'));
@ -115,7 +120,7 @@ module('Integration | Component | mount backend form', function (hooks) {
this.set('onMountSuccess', spy);
await render(
hbs`<MountBackendForm @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="auth" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
await mountBackend('approle', 'foo');
later(() => cancelTimers(), 50);
@ -146,10 +151,13 @@ module('Integration | Component | mount backend form', function (hooks) {
test('it renders secret engine specific headers', async function (assert) {
assert.expect(17);
await render(
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
assert.dom(GENERAL.title).hasText('Enable a Secrets Engine', 'renders secrets header');
for (const method of mountableEngines()) {
for (const method of filterEnginesByMountCategory({
mountCategory: 'secret',
isEnterprise: false,
}).filter((engine) => engine.type !== 'cubbyhole')) {
assert
.dom(MOUNT_BACKEND_FORM.mountType(method.type))
.hasText(method.displayName, `renders type:${method.displayName} picker`);
@ -158,7 +166,7 @@ module('Integration | Component | mount backend form', function (hooks) {
test('it changes path when type is changed', async function (assert) {
await render(
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
await click(MOUNT_BACKEND_FORM.mountType('azure'));
assert.dom(GENERAL.inputByAttr('path')).hasValue('azure', 'sets the value of the type');
@ -169,7 +177,7 @@ module('Integration | Component | mount backend form', function (hooks) {
test('it keeps path value if the user has changed it', async function (assert) {
await render(
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
await click(MOUNT_BACKEND_FORM.mountType('kv'));
assert.strictEqual(this.model.type, 'kv', 'Updates type on model');
@ -195,7 +203,7 @@ module('Integration | Component | mount backend form', function (hooks) {
this.set('onMountSuccess', spy);
await render(
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
await mountBackend('ssh', 'foo');
@ -212,7 +220,7 @@ module('Integration | Component | mount backend form', function (hooks) {
module('WIF secret engines', function () {
test('it shows identityTokenKey when type is a WIF engine and hides when its not', async function (assert) {
await render(
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
for (const engine of WIF_ENGINES) {
await click(MOUNT_BACKEND_FORM.mountType(engine));
@ -222,7 +230,10 @@ module('Integration | Component | mount backend form', function (hooks) {
.exists(`Identity token key field shows when type=${this.model.type}`);
await click(GENERAL.backButton);
}
for (const engine of mountableEngines().filter((e) => !WIF_ENGINES.includes(e.type))) {
for (const engine of filterEnginesByMountCategory({
mountCategory: 'secret',
isEnterprise: false,
}).filter((e) => !WIF_ENGINES.includes(e.type) && e.type !== 'cubbyhole')) {
// check non-wif engine
await click(MOUNT_BACKEND_FORM.mountType(engine.type));
await click(GENERAL.button('Method Options'));
@ -233,9 +244,9 @@ module('Integration | Component | mount backend form', function (hooks) {
}
});
test('it updates identityTokeKey if user has changed it', async function (assert) {
test('it updates identityTokenKey if user has changed it', async function (assert) {
await render(
hbs`<MountBackendForm @mountType="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
assert.strictEqual(
this.model.config.identityTokenKey,

View File

@ -8,15 +8,22 @@ import { setupRenderingTest } from 'vault/tests/helpers';
import { click, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import sinon from 'sinon';
import { allEngines, mountableEngines } from 'vault/helpers/mountable-secret-engines';
import { allMethods, methods } from 'vault/helpers/mountable-auth-methods';
import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import { setRunOptions } from 'ember-a11y-testing/test-support';
import { MOUNT_BACKEND_FORM } from 'vault/tests/helpers/components/mount-backend-form-selectors';
const secretTypes = mountableEngines().map((engine) => engine.type);
const allSecretTypes = allEngines().map((engine) => engine.type);
const authTypes = methods().map((auth) => auth.type);
const allAuthTypes = allMethods().map((auth) => auth.type);
const secretTypes = filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: false })
.filter((engine) => engine.type !== 'cubbyhole')
.map((engine) => engine.type);
const allSecretTypes = filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: true })
.filter((engine) => engine.type !== 'cubbyhole')
.map((engine) => engine.type);
const authTypes = filterEnginesByMountCategory({ mountCategory: 'auth', isEnterprise: false })
.filter((engine) => engine.type !== 'token')
.map((auth) => auth.type);
const allAuthTypes = filterEnginesByMountCategory({ mountCategory: 'auth', isEnterprise: true })
.filter((engine) => engine.type !== 'token')
.map((auth) => auth.type);
module('Integration | Component | mount-backend/type-form', function (hooks) {
setupRenderingTest(hooks);
@ -29,7 +36,7 @@ module('Integration | Component | mount-backend/type-form', function (hooks) {
assert.expect(secretTypes.length + 1, 'renders all mountable engines plus calls a spy');
const spy = sinon.spy();
this.set('setType', spy);
await render(hbs`<MountBackend::TypeForm @mountType="secret" @setMountType={{this.setType}} />`);
await render(hbs`<MountBackend::TypeForm @mountCategory="secret" @setMountType={{this.setType}} />`);
for (const type of secretTypes) {
assert.dom(MOUNT_BACKEND_FORM.mountType(type)).exists(`Renders ${type} mountable secret engine`);
@ -65,7 +72,7 @@ module('Integration | Component | mount-backend/type-form', function (hooks) {
'color-contrast': { enabled: false },
},
});
await render(hbs`<MountBackend::TypeForm @mountType="secret" @setMountType={{this.setType}} />`);
await render(hbs`<MountBackend::TypeForm @mountCategory="secret" @setMountType={{this.setType}} />`);
for (const type of allSecretTypes) {
assert.dom(MOUNT_BACKEND_FORM.mountType(type)).exists(`Renders ${type} secret engine`);
}
@ -73,7 +80,7 @@ module('Integration | Component | mount-backend/type-form', function (hooks) {
test('it renders correct items for enterprise auth methods', async function (assert) {
assert.expect(allAuthTypes.length, 'renders all enterprise auth engines');
await render(hbs`<MountBackend::TypeForm @mountType="auth" @setMountType={{this.setType}} />`);
await render(hbs`<MountBackend::TypeForm @mountCategory="auth" @setMountType={{this.setType}} />`);
for (const type of allAuthTypes) {
assert.dom(MOUNT_BACKEND_FORM.mountType(type)).exists(`Renders ${type} auth engine`);
}

View File

@ -6,16 +6,14 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { allEngines } from 'vault/helpers/mountable-secret-engines';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { CONFIGURABLE_SECRET_ENGINES } from 'vault/helpers/mountable-secret-engines';
import {
expectedConfigKeys,
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
import { ALL_ENGINES } from 'vault/utils/all-engines-metadata';
import engineDisplayData from 'vault/helpers/engines-display-data';
module('Integration | Component | SecretEngine::ConfigurationDetails', function (hooks) {
setupRenderingTest(hooks);
@ -60,10 +58,12 @@ module('Integration | Component | SecretEngine::ConfigurationDetails', function
.hasText(`Get started by configuring your Display Name secrets engine.`);
});
for (const type of CONFIGURABLE_SECRET_ENGINES) {
for (const type of ALL_ENGINES.filter((engine) => engine.isConfigurable ?? false).map(
(engine) => engine.type
)) {
test(`${type}: it shows config details if configModel(s) are passed in`, async function (assert) {
this.config = this.configs[type];
this.typeDisplay = allEnginesArray.find((engine) => engine.type === type).displayName;
this.typeDisplay = engineDisplayData(type).displayName;
await render(
hbs`<SecretEngine::ConfigurationDetails @config={{this.config}} @typeDisplay={{this.typeDisplay}}/>`

View File

@ -12,6 +12,7 @@ import { render, click, fillIn } from '@ember/test-helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { v4 as uuidv4 } from 'uuid';
import { hbs } from 'ember-cli-htmlbars';
import { ALL_ENGINES } from 'vault/utils/all-engines-metadata';
import { overrideResponse } from 'vault/tests/helpers/stubs';
import {
expectedConfigKeys,
@ -21,14 +22,14 @@ import {
fillInAwsConfig,
} from 'vault/tests/helpers/secret-engine/secret-engine-helpers';
import { capabilitiesStub } from 'vault/tests/helpers/stubs';
import { WIF_ENGINES, allEngines } from 'vault/helpers/mountable-secret-engines';
import engineDisplayData from 'vault/helpers/engines-display-data';
import waitForError from 'vault/tests/helpers/wait-for-error';
import AwsConfigForm from 'vault/forms/secrets/aws-config';
import AzureConfigForm from 'vault/forms/secrets/azure-config';
import GcpConfigForm from 'vault/forms/secrets/gcp-config';
import SshConfigForm from 'vault/forms/secrets/ssh-config';
const allEnginesArray = allEngines(); // saving as const so we don't invoke the method multiple times in the for loop
const WIF_ENGINES = ALL_ENGINES.filter((e) => e.isWIF).map((e) => e.type);
module('Integration | Component | SecretEngine::ConfigureWif', function (hooks) {
setupRenderingTest(hooks);
@ -73,7 +74,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
for (const type of WIF_ENGINES) {
test(`${type}: it renders default fields`, async function (assert) {
this.id = `${type}-${this.uid}`;
this.displayName = allEnginesArray.find((engine) => engine.type === type)?.displayName;
this.displayName = engineDisplayData(type).displayName;
this.form = this.getForm(type, {}, { isNew: true });
this.type = type;
@ -114,7 +115,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
for (const type of WIF_ENGINES) {
test(`${type}: it renders wif fields when user selects wif access type`, async function (assert) {
this.id = `${type}-${this.uid}`;
this.displayName = allEnginesArray.find((engine) => engine.type === type)?.displayName;
this.displayName = engineDisplayData(type).displayName;
this.form = this.getForm(type, {}, { isNew: true });
this.type = type;
@ -597,7 +598,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
for (const type of WIF_ENGINES) {
test(`${type}: it renders fields`, async function (assert) {
this.id = `${type}-${this.uid}`;
this.displayName = allEnginesArray.find((engine) => engine.type === type)?.displayName;
this.displayName = engineDisplayData(type).displayName;
this.form = this.getForm(type, {}, { isNew: true });
this.type = type;
@ -637,7 +638,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
for (const type of WIF_ENGINES) {
test(`${type}: it defaults to WIF accessType if WIF fields are already set`, async function (assert) {
this.id = `${type}-${this.uid}`;
this.displayName = allEnginesArray.find((engine) => engine.type === type)?.displayName;
this.displayName = engineDisplayData(type).displayName;
const config = createConfig(`${type}-wif`);
this.form = this.getForm(type, config);
this.type = type;
@ -658,7 +659,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
for (const type of WIF_ENGINES) {
test(`${type}: it renders issuer if global issuer is already set`, async function (assert) {
this.id = `${type}-${this.uid}`;
this.displayName = allEnginesArray.find((engine) => engine.type === type)?.displayName;
this.displayName = engineDisplayData(type).displayName;
this.issuer = 'https://foo-bar-blah.com';
const config = createConfig(`${type}-wif`);
this.form = this.getForm(type, { ...config, issuer: this.issuer });
@ -885,7 +886,7 @@ module('Integration | Component | SecretEngine::ConfigureWif', function (hooks)
this.id = `${type}-${this.uid}`;
const config = createConfig(`${type}-generic`);
this.form = this.getForm(type, config);
this.displayName = allEnginesArray.find((engine) => engine.type === type)?.displayName;
this.displayName = engineDisplayData(type).displayName;
this.type = type;
await this.renderComponent();

View File

@ -52,12 +52,12 @@ module('Integration | Component | secrets-engine-mount-config', function (hooks)
await click(selectors.toggle);
assert
.dom(GENERAL.infoRowValue('Secret Engine Type'))
.dom(GENERAL.infoRowValue('Secret engine type'))
.hasText(this.secretsEngine.engineType, 'Secret engine type renders');
assert.dom(GENERAL.infoRowValue('Path')).hasText(this.secretsEngine.path, 'Path renders');
assert.dom(GENERAL.infoRowValue('Accessor')).hasText(this.secretsEngine.accessor, 'Accessor renders');
assert.dom(GENERAL.infoRowValue('Local')).includesText('No', 'Local renders');
assert.dom(GENERAL.infoRowValue('Seal Wrap')).includesText('Yes', 'Seal wrap renders');
assert.dom(GENERAL.infoRowValue('Seal wrap')).includesText('Yes', 'Seal wrap renders');
assert.dom(GENERAL.infoRowValue('Default Lease TTL')).includesText('0', 'Default Lease TTL renders');
assert
.dom(GENERAL.infoRowValue('Max Lease TTL'))

View File

@ -25,6 +25,7 @@ module('Unit | Model | secret-engine', function (hooks) {
'path',
'description',
'accessor',
'runningPluginVersion',
'local',
'sealWrap',
'config.defaultLeaseTtl',
@ -48,6 +49,7 @@ module('Unit | Model | secret-engine', function (hooks) {
'path',
'description',
'accessor',
'runningPluginVersion',
'local',
'sealWrap',
'config.defaultLeaseTtl',
@ -73,6 +75,7 @@ module('Unit | Model | secret-engine', function (hooks) {
'path',
'description',
'accessor',
'runningPluginVersion',
'local',
'sealWrap',
'config.defaultLeaseTtl',
@ -100,6 +103,7 @@ module('Unit | Model | secret-engine', function (hooks) {
'path',
'description',
'accessor',
'runningPluginVersion',
'local',
'sealWrap',
'config.allowedManagedKeys',
@ -121,6 +125,7 @@ module('Unit | Model | secret-engine', function (hooks) {
'path',
'description',
'accessor',
'runningPluginVersion',
'local',
'sealWrap',
'config.defaultLeaseTtl',

View File

@ -27,6 +27,7 @@ export default class SecretEngineModel extends Model {
maxVersions: number;
casRequired: boolean;
deleteVersionAfter: string;
runningPluginVersion: string;
get modelTypeForKV(): string;
get isV2KV(): boolean;
get attrs(): Array<FormField>;