diff --git a/changelog/28822.txt b/changelog/28822.txt
new file mode 100644
index 0000000000..7f8f48fe72
--- /dev/null
+++ b/changelog/28822.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: Add identity_token_key to mount view for the GCP and Azure Secret engines.
+```
diff --git a/ui/app/helpers/mountable-secret-engines.js b/ui/app/helpers/mountable-secret-engines.js
index 216eb960fa..ca9341a68c 100644
--- a/ui/app/helpers/mountable-secret-engines.js
+++ b/ui/app/helpers/mountable-secret-engines.js
@@ -134,8 +134,8 @@ const MOUNTABLE_SECRET_ENGINES = [
},
];
-// A list of Workload Identity Federation engines. Will eventually include Azure and GCP.
-export const WIF_ENGINES = ['aws'];
+// A list of Workflow Identity Federation engines.
+export const WIF_ENGINES = ['aws', 'azure', 'gcp'];
export function wifEngines() {
return WIF_ENGINES.slice();
diff --git a/ui/app/models/secret-engine.js b/ui/app/models/secret-engine.js
index 86d497281e..b64cebeed3 100644
--- a/ui/app/models/secret-engine.js
+++ b/ui/app/models/secret-engine.js
@@ -9,7 +9,7 @@ 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 } from 'vault/helpers/mountable-secret-engines';
+import { isAddonEngine, allEngines, WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
import { WHITESPACE_WARNING } from 'vault/utils/model-helpers/validators';
const LINKED_BACKENDS = supportedSecretBackends();
@@ -179,8 +179,8 @@ export default class SecretEngineModel extends Model {
if (type === 'kv' && parseInt(this.version, 10) === 2) {
fields.push('casRequired', 'deleteVersionAfter', 'maxVersions');
}
- // WIF secret engines
- if (type === 'aws') {
+ // For WIF Secret engines, allow users to set the identity token key when mounting the engine.
+ if (WIF_ENGINES.includes(type)) {
fields.push('config.identityTokenKey');
}
return fields;
@@ -232,7 +232,7 @@ export default class SecretEngineModel extends Model {
// no ttl options for keymgmt
optionFields = [...CORE_OPTIONS, 'config.allowedManagedKeys', ...STANDARD_CONFIG];
break;
- case 'aws':
+ case WIF_ENGINES.find((type) => type === this.engineType):
defaultFields = ['path'];
optionFields = [
...CORE_OPTIONS,
diff --git a/ui/tests/acceptance/settings/mount-secret-backend-test.js b/ui/tests/acceptance/settings/mount-secret-backend-test.js
index 6a648b4514..8894067f16 100644
--- a/ui/tests/acceptance/settings/mount-secret-backend-test.js
+++ b/ui/tests/acceptance/settings/mount-secret-backend-test.js
@@ -34,7 +34,6 @@ 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 { WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
const consoleComponent = create(consoleClass);
@@ -324,23 +323,19 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
// create an oidc/key
await runCmd(`write identity/oidc/key/some-key allowed_client_ids="*"`);
- for (const engine of WIF_ENGINES) {
- await page.visit();
- await click(MOUNT_BACKEND_FORM.mountType(engine));
- await click(GENERAL.toggleGroup('Method Options'));
- assert
- .dom('[data-test-search-select-with-modal]')
- .exists('Search select with modal component renders');
- await clickTrigger('#key');
- const dropdownOptions = findAll('[data-option-index]').map((o) => o.innerText);
- assert.ok(dropdownOptions.includes('some-key'), 'search select options show some-key');
- await click(GENERAL.searchSelect.option(GENERAL.searchSelect.optionIndex('some-key')));
- assert
- .dom(GENERAL.searchSelect.selectedOption())
- .hasText('some-key', 'some-key was selected and displays in the search select');
- }
- // Go back and choose a non-wif engine type
+ await page.visit();
+ await click(MOUNT_BACKEND_FORM.mountType('aws')); // only testing aws of the WIF engines as the functionality for all others WIF engines in this form are the same
+ await click(GENERAL.toggleGroup('Method Options'));
+ assert.dom('[data-test-search-select-with-modal]').exists('Search select with modal component renders');
+ await clickTrigger('#key');
+ const dropdownOptions = findAll('[data-option-index]').map((o) => o.innerText);
+ assert.ok(dropdownOptions.includes('some-key'), 'search select options show some-key');
+ await click(GENERAL.searchSelect.option(GENERAL.searchSelect.optionIndex('some-key')));
+ assert
+ .dom(GENERAL.searchSelect.selectedOption())
+ .hasText('some-key', 'some-key was selected and displays in the search select');
await click(GENERAL.backButton);
+ // Choose a non-wif engine
await click(MOUNT_BACKEND_FORM.mountType('ssh'));
assert
.dom('[data-test-search-select-with-modal]')
@@ -350,75 +345,84 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
});
test('it allows a user with permissions to oidc/key to create an identity_token_key', async function (assert) {
- for (const engine of WIF_ENGINES) {
- const path = `secrets-adminPolicy-${engine}`;
- const newKey = `key-${uuidv4()}`;
- const secrets_admin_policy = adminOidcCreateRead(path);
- const secretsAdminToken = await runCmd(
- tokenWithPolicyCmd(`secrets-admin-${path}`, secrets_admin_policy)
- );
+ logout.visit();
+ const engine = 'aws'; // only testing aws of the WIF engines as the functionality for all others WIF engines in this form are the same
+ await authPage.login();
+ const path = `secrets-adminPolicy-${engine}`;
+ const newKey = `key-${engine}-${uuidv4()}`;
+ const secrets_admin_policy = adminOidcCreateRead(path);
+ const secretsAdminToken = await runCmd(
+ tokenWithPolicyCmd(`secrets-admin-${path}`, secrets_admin_policy)
+ );
- await logout.visit();
- await authPage.login(secretsAdminToken);
- await page.visit();
- await click(MOUNT_BACKEND_FORM.mountType(engine));
- await fillIn(GENERAL.inputByAttr('path'), path);
- await click(GENERAL.toggleGroup('Method Options'));
- await clickTrigger('#key');
- // create new key
- await fillIn(GENERAL.searchSelect.searchInput, newKey);
- await click(GENERAL.searchSelect.options);
- assert.dom('#search-select-modal').exists('modal with form opens');
- assert.dom('[data-test-modal-title]').hasText('Create new key', 'Create key modal renders');
+ await logout.visit();
+ await authPage.login(secretsAdminToken);
+ await visit('/vault/settings/mount-secret-backend');
+ await click(MOUNT_BACKEND_FORM.mountType(engine));
+ await fillIn(GENERAL.inputByAttr('path'), path);
+ await click(GENERAL.toggleGroup('Method Options'));
+ await clickTrigger('#key');
+ // create new key
+ await fillIn(GENERAL.searchSelect.searchInput, newKey);
+ await click(GENERAL.searchSelect.options);
+ assert.dom('#search-select-modal').exists(`modal with form opens for engine ${engine}`);
+ assert
+ .dom('[data-test-modal-title]')
+ .hasText('Create new key', `Create key modal renders for engine: ${engine}`);
- await click(OIDC.keySaveButton);
- assert.dom('#search-select-modal').doesNotExist('modal disappears onSave');
- assert.dom(GENERAL.searchSelect.selectedOption()).hasText(newKey, `${newKey} is now selected`);
+ await click(OIDC.keySaveButton);
+ assert.dom('#search-select-modal').doesNotExist(`modal disappears onSave for engine ${engine}`);
+ assert.dom(GENERAL.searchSelect.selectedOption()).hasText(newKey, `${newKey} is now selected`);
- await click(GENERAL.saveButton);
- await visit(`/vault/secrets/${path}/configuration`);
- await click(SES.configurationToggle);
- assert
- .dom(GENERAL.infoRowValue('Identity Token Key'))
- .hasText(newKey, 'shows identity token key on configuration page');
- // cleanup
- await runCmd(`delete sys/mounts/${path}`);
- await runCmd(`delete identity/oidc/key/some-key`);
- await runCmd(`delete identity/oidc/key/${newKey}`);
- }
+ await click(GENERAL.saveButton);
+ await visit(`/vault/secrets/${path}/configuration`);
+ await click(SES.configurationToggle);
+ assert
+ .dom(GENERAL.infoRowValue('Identity Token Key'))
+ .hasText(newKey, `shows identity token key on configuration page for engine: ${engine}`);
+
+ // cleanup
+ await runCmd(`delete sys/mounts/${path}`);
+ await runCmd(`delete identity/oidc/key/some-key`);
+ await runCmd(`delete identity/oidc/key/${newKey}`);
+ await logout.visit();
});
test('it allows user with NO access to oidc/key to manually input an identity_token_key', async function (assert) {
- for (const engine of WIF_ENGINES) {
- const path = `secrets-noOidcAdmin-${engine}`;
- const secretsNoOidcAdminPolicy = adminOidcCreate(path);
- const secretsNoOidcAdminToken = await runCmd(
- tokenWithPolicyCmd(`secrets-noOidcAdmin-${path}`, secretsNoOidcAdminPolicy)
- );
- // create an oidc/key that they can then use even if they can't read it.
- await runCmd(`write identity/oidc/key/general-key allowed_client_ids="*"`);
+ await logout.visit();
+ const engine = 'aws'; // only testing aws of the WIF engines as the functionality for all others WIF engines in this form are the same
+ await authPage.login();
+ const path = `secrets-noOidcAdmin-${engine}`;
+ const secretsNoOidcAdminPolicy = adminOidcCreate(path);
+ const secretsNoOidcAdminToken = await runCmd(
+ tokenWithPolicyCmd(`secrets-noOidcAdmin-${path}`, secretsNoOidcAdminPolicy)
+ );
+ // create an oidc/key that they can then use even if they can't read it.
+ await runCmd(`write identity/oidc/key/general-key allowed_client_ids="*"`);
- await logout.visit();
- await authPage.login(secretsNoOidcAdminToken);
- await page.visit();
- await click(MOUNT_BACKEND_FORM.mountType(engine));
- await fillIn(GENERAL.inputByAttr('path'), path);
- await click(GENERAL.toggleGroup('Method Options'));
- // type-in fallback component to create new key
- await typeIn(GENERAL.inputSearch('key'), 'general-key');
- await click(GENERAL.saveButton);
- assert
- .dom(GENERAL.latestFlashContent)
- .hasText(`Successfully mounted the aws secrets engine at ${path}.`);
+ await logout.visit();
+ await authPage.login(secretsNoOidcAdminToken);
+ await page.visit();
+ await click(MOUNT_BACKEND_FORM.mountType(engine));
+ await fillIn(GENERAL.inputByAttr('path'), path);
+ await click(GENERAL.toggleGroup('Method Options'));
+ // type-in fallback component to create new key
+ await typeIn(GENERAL.inputSearch('key'), 'general-key');
+ await click(GENERAL.saveButton);
+ assert
+ .dom(GENERAL.latestFlashContent)
+ .hasText(`Successfully mounted the ${engine} secrets engine at ${path}.`);
- await visit(`/vault/secrets/${path}/configuration`);
- await click(SES.configurationToggle);
- assert
- .dom(GENERAL.infoRowValue('Identity Token Key'))
- .hasText('general-key', 'shows identity token key on configuration page');
- // cleanup
- await runCmd(`delete sys/mounts/${path}`);
- }
+ await visit(`/vault/secrets/${path}/configuration`);
+
+ await click(SES.configurationToggle);
+ assert
+ .dom(GENERAL.infoRowValue('Identity Token Key'))
+ .hasText('general-key', `shows identity token key on configuration page for engine: ${engine}`);
+
+ // cleanup
+ await runCmd(`delete sys/mounts/${path}`);
+ await logout.visit();
});
});
});
diff --git a/ui/tests/helpers/secret-engine/policy-generator.ts b/ui/tests/helpers/secret-engine/policy-generator.ts
index 5f758bbb88..d8a08e55d3 100644
--- a/ui/tests/helpers/secret-engine/policy-generator.ts
+++ b/ui/tests/helpers/secret-engine/policy-generator.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-// This is policy can mount a secret engine
+// This policy can mount a secret engine
// and list and create oidc keys, relevant for setting identity_key_token for WIF
export const adminOidcCreateRead = (mountPath: string) => {
return `
diff --git a/ui/tests/integration/components/mount-backend-form-test.js b/ui/tests/integration/components/mount-backend-form-test.js
index adcc1079f4..5579d36ea5 100644
--- a/ui/tests/integration/components/mount-backend-form-test.js
+++ b/ui/tests/integration/components/mount-backend-form-test.js
@@ -13,9 +13,8 @@ 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 } from 'vault/helpers/mountable-secret-engines';
+import { mountableEngines, WIF_ENGINES } from 'vault/helpers/mountable-secret-engines';
import hbs from 'htmlbars-inline-precompile';
-
import sinon from 'sinon';
module('Integration | Component | mount backend form', function (hooks) {
@@ -202,43 +201,50 @@ module('Integration | Component | mount backend form', function (hooks) {
});
module('WIF secret engines', function () {
- test('it shows identityTokenKey when type is aws and hides when its not', async function (assert) {
+ test('it shows identityTokenKey when type is a WIF engine and hides when its not', async function (assert) {
await render(
hbs``
);
- await click(MOUNT_BACKEND_FORM.mountType('ldap'));
-
- await click(GENERAL.toggleGroup('Method Options'));
- assert
- .dom(GENERAL.fieldByAttr('identityTokenKey'))
- .doesNotExist(`Identity token key field hidden when type=${this.model.type}`);
-
- await click(GENERAL.backButton);
- await click(MOUNT_BACKEND_FORM.mountType('aws'));
- await click(GENERAL.toggleGroup('Method Options'));
- assert
- .dom(GENERAL.fieldByAttr('identityTokenKey'))
- .exists(`Identity token key field shows when type=${this.model.type}`);
+ for (const engine of WIF_ENGINES) {
+ await click(MOUNT_BACKEND_FORM.mountType(engine));
+ await click(GENERAL.toggleGroup('Method Options'));
+ assert
+ .dom(GENERAL.fieldByAttr('identityTokenKey'))
+ .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))) {
+ // check non-wif engine
+ await click(MOUNT_BACKEND_FORM.mountType(engine.type));
+ await click(GENERAL.toggleGroup('Method Options'));
+ assert
+ .dom(GENERAL.fieldByAttr('identityTokenKey'))
+ .doesNotExist(`Identity token key field hidden when type=${this.model.type}`);
+ await click(GENERAL.backButton);
+ }
});
test('it updates identityTokeKey if user has changed it', async function (assert) {
await render(
hbs``
);
- await click(MOUNT_BACKEND_FORM.mountType('aws'));
assert.strictEqual(
this.model.config.identityTokenKey,
undefined,
- 'On init identityTokenKey is not set on the model'
+ `On init identityTokenKey is not set on the model`
);
+ for (const engine of WIF_ENGINES) {
+ await click(MOUNT_BACKEND_FORM.mountType(engine));
+ await click(GENERAL.toggleGroup('Method Options'));
+ await typeIn(GENERAL.inputSearch('key'), `${engine}+specialKey`); // set to something else
- await click(GENERAL.toggleGroup('Method Options'));
- await typeIn(GENERAL.inputSearch('key'), 'default');
- assert.strictEqual(
- this.model.config.identityTokenKey,
- 'default',
- 'updates model with default identityTokenKey'
- );
+ assert.strictEqual(
+ this.model.config.identityTokenKey,
+ `${engine}+specialKey`,
+ `updates ${engine} model with custom identityTokenKey`
+ );
+ await click(GENERAL.backButton);
+ }
});
});
});