mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-29 09:42:25 +00:00 
			
		
		
		
	 3abca46464
			
		
	
	3abca46464
	
	
	
		
			
			* manual cherry pick to deal with all the merge things * changelog * test fixes * Update 28148.txt * fix tests failures after main merge * fix test failures after main merge * Add Access Type and conditionally render WIF fields (#28149) * initial work. * remove access_type * better no model logic well kind of * rollback attrs * remove defaults * stopping point * wip changing back to sidebranch * hustling shuffling and serializing * some of the component test coverage * disable acces type if editing * test coverage * hide max retries that sneaky bugger * cleanup * cleanup * Update root-config.js * remove flash message check, locally passes great but on ci flaky * clean up * thank you chelsea * test clean up per enterprise vs community * address pr comments * welp a miss add * UI (sidebranch) WIF Issuer field (#28187) * Add type declaration files for aws config models * use updated task syntax for save method on configure-aws * fix types on edit route * fetch issuer on configure edit page if aws + enterprise * track issuer within configure-aws component * add placeholder support on form-field * Add warning if issuer changed from previous value or could not be read * cleanup * preliminary tests * dont use while loop so we can test the modal * tests * cleanup * fix tests * remove extra tracked value and duplicate changed attrs check * modal footer --------- Co-authored-by: Angel Garbarino <argarbarino@gmail.com> * Display issuer on Configuration details (#28209) * display issuer on configuration details * workflow complete, now on to testing * handle issuer things * fix all the broken tests things * add test coveragE: * cleanup * rename model/adapter * Update configure-aws.ts * Update aws-configuration-test.js * 90 percent there for pr comments * last one for tonight * a few more because why not * hasDirtyAttributes fixes * revert back to previous noRead->queryIssuerError --------- Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
		
			
				
	
	
		
			421 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			421 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Copyright (c) HashiCorp, Inc.
 | |
|  * SPDX-License-Identifier: BUSL-1.1
 | |
|  */
 | |
| 
 | |
| import {
 | |
|   currentRouteName,
 | |
|   currentURL,
 | |
|   settled,
 | |
|   click,
 | |
|   findAll,
 | |
|   fillIn,
 | |
|   visit,
 | |
|   typeIn,
 | |
| } from '@ember/test-helpers';
 | |
| import { clickTrigger } from 'ember-power-select/test-support/helpers';
 | |
| import { module, test } from 'qunit';
 | |
| import { setupApplicationTest } from 'ember-qunit';
 | |
| import { v4 as uuidv4 } from 'uuid';
 | |
| import { runCmd, tokenWithPolicyCmd } from 'vault/tests/helpers/commands';
 | |
| 
 | |
| import { create } from 'ember-cli-page-object';
 | |
| import page from 'vault/tests/pages/settings/mount-secret-backend';
 | |
| import configPage from 'vault/tests/pages/secrets/backend/configuration';
 | |
| import authPage from 'vault/tests/pages/auth';
 | |
| import consoleClass from 'vault/tests/pages/components/console/ui-panel';
 | |
| import logout from 'vault/tests/pages/logout';
 | |
| import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
 | |
| import { mountableEngines } from 'vault/helpers/mountable-secret-engines'; // allEngines() includes enterprise engines, those are tested elsewhere
 | |
| 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';
 | |
| 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);
 | |
| 
 | |
| // enterprise backends are tested separately
 | |
| const BACKENDS_WITH_ENGINES = ['kv', 'pki', 'ldap', 'kubernetes'];
 | |
| module('Acceptance | settings/mount-secret-backend', function (hooks) {
 | |
|   setupApplicationTest(hooks);
 | |
| 
 | |
|   hooks.beforeEach(function () {
 | |
|     this.uid = uuidv4();
 | |
|     this.calcDays = (hours) => {
 | |
|       const days = Math.floor(hours / 24);
 | |
|       const remainder = hours % 24;
 | |
|       return `${days} days ${remainder} hours`;
 | |
|     };
 | |
|     return authPage.login();
 | |
|   });
 | |
| 
 | |
|   test('it sets the ttl correctly when mounting', async function (assert) {
 | |
|     // always force the new mount to the top of the list
 | |
|     const path = `mount-kv-${this.uid}`;
 | |
|     const defaultTTLHours = 100;
 | |
|     const maxTTLHours = 300;
 | |
|     await page.visit();
 | |
| 
 | |
|     assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
 | |
|     await page.selectType('kv');
 | |
|     await page
 | |
|       .path(path)
 | |
|       .toggleOptions()
 | |
|       .enableDefaultTtl()
 | |
|       .defaultTTLUnit('h')
 | |
|       .defaultTTLVal(defaultTTLHours)
 | |
|       .enableMaxTtl()
 | |
|       .maxTTLUnit('h')
 | |
|       .maxTTLVal(maxTTLHours)
 | |
|       .submit();
 | |
|     await configPage.visit({ backend: path });
 | |
|     assert.strictEqual(configPage.defaultTTL, `${this.calcDays(defaultTTLHours)}`, 'shows the proper TTL');
 | |
|     assert.strictEqual(configPage.maxTTL, `${this.calcDays(maxTTLHours)}`, 'shows the proper max TTL');
 | |
|   });
 | |
| 
 | |
|   test('it sets the ttl when enabled then disabled', async function (assert) {
 | |
|     // always force the new mount to the top of the list
 | |
|     const path = `mount-kv-${this.uid}`;
 | |
|     const maxTTLHours = 300;
 | |
| 
 | |
|     await page.visit();
 | |
| 
 | |
|     assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
 | |
|     await page.selectType('kv');
 | |
|     await page
 | |
|       .path(path)
 | |
|       .toggleOptions()
 | |
|       .enableDefaultTtl()
 | |
|       .enableMaxTtl()
 | |
|       .maxTTLUnit('h')
 | |
|       .maxTTLVal(maxTTLHours)
 | |
|       .submit();
 | |
|     await configPage.visit({ backend: path });
 | |
|     assert.strictEqual(configPage.defaultTTL, '1 month 1 day', 'shows system default TTL');
 | |
|     assert.strictEqual(configPage.maxTTL, `${this.calcDays(maxTTLHours)}`, 'shows the proper max TTL');
 | |
|   });
 | |
| 
 | |
|   test('it sets the max ttl after pki chosen, resets after', async function (assert) {
 | |
|     await page.visit();
 | |
|     assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
 | |
|     await page.selectType('pki');
 | |
|     assert.dom('[data-test-input="maxLeaseTtl"]').exists();
 | |
|     assert
 | |
|       .dom('[data-test-input="maxLeaseTtl"] [data-test-ttl-toggle]')
 | |
|       .isChecked('Toggle is checked by default');
 | |
|     assert.dom('[data-test-input="maxLeaseTtl"] [data-test-ttl-value]').hasValue('3650');
 | |
|     assert.dom('[data-test-input="maxLeaseTtl"] [data-test-select="ttl-unit"]').hasValue('d');
 | |
| 
 | |
|     // Go back and choose a different type
 | |
|     await page.back();
 | |
|     await page.selectType('database');
 | |
|     assert.dom('[data-test-input="maxLeaseTtl"]').exists('3650');
 | |
|     assert
 | |
|       .dom('[data-test-input="maxLeaseTtl"] [data-test-ttl-toggle]')
 | |
|       .isNotChecked('Toggle is unchecked by default');
 | |
|     await page.enableMaxTtl();
 | |
|     assert.dom('[data-test-input="maxLeaseTtl"] [data-test-ttl-value]').hasValue('');
 | |
|     assert.dom('[data-test-input="maxLeaseTtl"] [data-test-select="ttl-unit"]').hasValue('s');
 | |
|   });
 | |
| 
 | |
|   test('it throws error if setting duplicate path name', async function (assert) {
 | |
|     const path = `kv-duplicate`;
 | |
| 
 | |
|     await consoleComponent.runCommands([
 | |
|       // delete any kv-duplicate previously written here so that tests can be re-run
 | |
|       `delete sys/mounts/${path}`,
 | |
|     ]);
 | |
| 
 | |
|     await page.visit();
 | |
| 
 | |
|     assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
 | |
|     await page.selectType('kv');
 | |
|     await page.path(path).submit();
 | |
|     await page.secretList();
 | |
|     await settled();
 | |
|     await page.enableEngine();
 | |
|     await page.selectType('kv');
 | |
|     await page.path(path).submit();
 | |
|     assert.dom('[data-test-message-error-description]').containsText(`path is already in use at ${path}`);
 | |
|     assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
 | |
| 
 | |
|     await page.secretList();
 | |
|     await settled();
 | |
|     assert
 | |
|       .dom(`[data-test-secrets-backend-link=${path}]`)
 | |
|       .exists({ count: 1 }, 'renders only one instance of the engine');
 | |
|   });
 | |
| 
 | |
|   test('version 2 with no update to config endpoint still allows mount of secret engine', async function (assert) {
 | |
|     const enginePath = `kv-noUpdate-${this.uid}`;
 | |
|     const V2_POLICY = `
 | |
|       path "${enginePath}/*" {
 | |
|         capabilities = ["list","create","read","sudo","delete"]
 | |
|       }
 | |
|       path "sys/mounts/*"
 | |
|       {
 | |
|         capabilities = ["create", "read", "update", "delete", "list", "sudo"]
 | |
|       }
 | |
| 
 | |
|       # List existing secrets engines.
 | |
|       path "sys/mounts"
 | |
|       {
 | |
|         capabilities = ["read"]
 | |
|       }
 | |
|       # Allow page to load after mount
 | |
|       path "sys/internal/ui/mounts/${enginePath}" {
 | |
|         capabilities = ["read"]
 | |
|       }
 | |
|     `;
 | |
|     await consoleComponent.toggle();
 | |
|     await consoleComponent.runCommands(
 | |
|       [
 | |
|         // delete any previous mount with same name
 | |
|         `delete sys/mounts/${enginePath}`,
 | |
|         `write sys/policies/acl/kv-v2-degrade policy=${btoa(V2_POLICY)}`,
 | |
|         'write -field=client_token auth/token/create policies=kv-v2-degrade',
 | |
|       ],
 | |
|       false
 | |
|     );
 | |
|     await settled();
 | |
|     const userToken = consoleComponent.lastLogOutput;
 | |
|     await logout.visit();
 | |
|     await authPage.login(userToken);
 | |
|     // create the engine
 | |
|     await mountSecrets.visit();
 | |
|     await mountSecrets.selectType('kv');
 | |
|     await mountSecrets.path(enginePath).setMaxVersion(101).submit();
 | |
|     await settled();
 | |
|     assert
 | |
|       .dom('[data-test-flash-message]')
 | |
|       .containsText(
 | |
|         `You do not have access to the config endpoint. The secret engine was mounted, but the configuration settings were not saved.`
 | |
|       );
 | |
|     assert.strictEqual(
 | |
|       currentURL(),
 | |
|       `/vault/secrets/${enginePath}/kv/list`,
 | |
|       'After mounting, redirects to secrets list page'
 | |
|     );
 | |
|     await configPage.visit({ backend: enginePath });
 | |
|     await settled();
 | |
|   });
 | |
| 
 | |
|   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));
 | |
|     assert.expect(addons.length);
 | |
| 
 | |
|     for (const engine of addons) {
 | |
|       await consoleComponent.runCommands([
 | |
|         // delete any previous mount with same name
 | |
|         `delete sys/mounts/${engine.type}`,
 | |
|       ]);
 | |
|       await mountSecrets.visit();
 | |
|       await mountSecrets.selectType(engine.type);
 | |
|       await mountSecrets.path(engine.type).submit();
 | |
|       assert.strictEqual(
 | |
|         currentRouteName(),
 | |
|         `vault.cluster.secrets.backend.${engine.engineRoute}`,
 | |
|         `Transitions to ${engine.displayName} route on mount success`
 | |
|       );
 | |
|       await consoleComponent.runCommands([
 | |
|         // cleanup after
 | |
|         `delete sys/mounts/${engine.type}`,
 | |
|       ]);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   test('it should transition to mountable non-addon engine after mount success', async function (assert) {
 | |
|     // 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');
 | |
|     assert.expect(engines.length);
 | |
| 
 | |
|     for (const engine of engines) {
 | |
|       await consoleComponent.runCommands([
 | |
|         // delete any previous mount with same name
 | |
|         `delete sys/mounts/${engine.type}`,
 | |
|       ]);
 | |
|       await mountSecrets.visit();
 | |
|       await mountSecrets.selectType(engine.type);
 | |
|       await mountSecrets.path(engine.type);
 | |
|       if (engine.type === 'kv') {
 | |
|         await mountSecrets.toggleOptions().version(1);
 | |
|       }
 | |
|       await mountSecrets.submit();
 | |
| 
 | |
|       assert.strictEqual(
 | |
|         currentRouteName(),
 | |
|         `vault.cluster.secrets.backend.list-root`,
 | |
|         `${engine.type} navigates to list view`
 | |
|       );
 | |
|       await consoleComponent.runCommands([
 | |
|         // cleanup after
 | |
|         `delete sys/mounts/${engine.type}`,
 | |
|       ]);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   test('it should transition back to backend list for unsupported backends', async function (assert) {
 | |
|     const unsupported = mountableEngines().filter((e) => !supportedSecretBackends().includes(e.type));
 | |
|     assert.expect(unsupported.length);
 | |
| 
 | |
|     for (const engine of unsupported) {
 | |
|       await consoleComponent.runCommands([
 | |
|         // delete any previous mount with same name
 | |
|         `delete sys/mounts/${engine.type}`,
 | |
|       ]);
 | |
|       await mountSecrets.visit();
 | |
|       await mountSecrets.selectType(engine.type);
 | |
|       await mountSecrets.path(engine.type).submit();
 | |
| 
 | |
|       assert.strictEqual(
 | |
|         currentRouteName(),
 | |
|         `vault.cluster.secrets.backends`,
 | |
|         `${engine.type} returns to backends list`
 | |
|       );
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   test('it should transition to different locations for kv v1 and v2', async function (assert) {
 | |
|     assert.expect(4);
 | |
|     const v2 = 'kv-v2';
 | |
|     await consoleComponent.runCommands([
 | |
|       // delete any previous mount with same name
 | |
|       `delete sys/mounts/${v2}`,
 | |
|     ]);
 | |
|     await mountSecrets.visit();
 | |
|     await mountSecrets.selectType('kv');
 | |
|     await mountSecrets.path(v2).submit();
 | |
| 
 | |
|     assert.strictEqual(currentURL(), `/vault/secrets/${v2}/kv/list`, `${v2} navigates to list url`);
 | |
|     assert.strictEqual(
 | |
|       currentRouteName(),
 | |
|       `vault.cluster.secrets.backend.kv.list`,
 | |
|       `${v2} navigates to list url`
 | |
|     );
 | |
| 
 | |
|     const v1 = 'kv-v1';
 | |
|     await consoleComponent.runCommands([
 | |
|       // delete any previous mount with same name
 | |
|       `delete sys/mounts/${v1}`,
 | |
|     ]);
 | |
|     await mountSecrets.visit();
 | |
|     await mountSecrets.selectType('kv');
 | |
|     await mountSecrets.path(v1).toggleOptions().version(1).submit();
 | |
| 
 | |
|     assert.strictEqual(currentURL(), `/vault/secrets/${v1}/list`, `${v1} navigates to list url`);
 | |
|     assert.strictEqual(
 | |
|       currentRouteName(),
 | |
|       `vault.cluster.secrets.backend.list-root`,
 | |
|       `${v1} navigates to list route`
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   module('WIF secret engines', function () {
 | |
|     test('it sets identity_token_key on mount config using search select list, resets after', async function (assert) {
 | |
|       // 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 page.selectType(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.back();
 | |
|       await page.selectType('ssh');
 | |
|       assert
 | |
|         .dom('[data-test-search-select-with-modal]')
 | |
|         .doesNotExist('for type ssh, the modal field does not render.');
 | |
|       // cleanup
 | |
|       await runCmd(`delete identity/oidc/key/some-key`);
 | |
|     });
 | |
| 
 | |
|     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)
 | |
|         );
 | |
| 
 | |
|         await logout.visit();
 | |
|         await authPage.login(secretsAdminToken);
 | |
|         await page.visit();
 | |
|         await page.selectType(engine);
 | |
|         await page.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 click(OIDC.keySaveButton);
 | |
|         assert.dom('#search-select-modal').doesNotExist('modal disappears onSave');
 | |
|         assert.dom(GENERAL.searchSelect.selectedOption()).hasText(newKey, `${newKey} is now selected`);
 | |
| 
 | |
|         await page.submit();
 | |
|         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}`);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     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();
 | |
|         await authPage.login(secretsNoOidcAdminToken);
 | |
|         await page.visit();
 | |
|         await page.selectType(engine);
 | |
|         await page.path(path);
 | |
|         await click(GENERAL.toggleGroup('Method Options'));
 | |
|         // type-in fallback component to create new key
 | |
|         await typeIn(GENERAL.inputSearch('key'), 'general-key');
 | |
|         await page.submit();
 | |
|         assert
 | |
|           .dom(GENERAL.latestFlashContent)
 | |
|           .hasText(`Successfully mounted the aws 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}`);
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| });
 |