diff --git a/ui/e2e/tests/superuser/keymgmt-mount-external.ent.spec.ts b/ui/e2e/tests/superuser/keymgmt-mount-external.ent.spec.ts new file mode 100644 index 0000000000..d7245f0898 --- /dev/null +++ b/ui/e2e/tests/superuser/keymgmt-mount-external.ent.spec.ts @@ -0,0 +1,136 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { expect, test } from '@playwright/test'; + +const PINNED_PLUGIN_DATA = { + data: { + name: 'vault-plugin-secrets-keymgmt', + type: 'secret', + version: 'v0.17.0+ent', + }, +}; + +const PLUGIN_CATALOG_DATA = { + request_id: 'request_id', + lease_id: '', + renewable: false, + lease_duration: 0, + data: { + detailed: [ + { + builtin: true, + deprecation_status: 'supported', + name: 'keymgmt', + type: 'secret', + version: 'v0.18.1+builtin', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: '', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: 'v0.16.0+ent', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: 'v0.17.0+ent', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: 'v0.18.0+ent', + }, + ], + }, +}; + +test('mount external keymgmt workflow', async ({ page }) => { + await test.step('mock the keymgmt pinned version response', async () => { + await page.route('**v1/sys/plugins/pins/secret/vault-plugin-secrets-keymgmt', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(PINNED_PLUGIN_DATA), + }); + } else { + await route.continue(); + } + }); + }); + + await test.step('mock the plugin catalog response to return builtin and external keymgmt plugins', async () => { + await page.route('**v1/sys/plugins/catalog', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(PLUGIN_CATALOG_DATA), + }); + } else { + await route.continue(); + } + }); + }); + + await page.goto('dashboard'); + + await test.step('navigate to enable Key Management engine', async () => { + await page.getByRole('link', { name: 'Secrets', exact: true }).click(); + await page.getByRole('link', { name: 'Enable new engine' }).click(); + await page.getByLabel('Key Management - enabled').click(); + await page.getByRole('textbox', { name: 'Path' }).fill('keymgmt-external'); + }); + + await test.step('verify builtin and external plugin type options are visible', async () => { + await expect(page.getByText('Built-in plugin Preregistered')).toBeVisible(); + await expect(page.getByText('External plugin External')).toBeVisible(); + await expect(page.getByText('Plugin version Required')).not.toBeVisible(); + }); + + await test.step('selecting external plugin type shows plugin version dropdown', async () => { + await page.locator('label:nth-child(2) > .hds-form-radio-card__control-wrapper').click(); + await expect(page.getByText('Plugin version Required')).toBeVisible(); + await expect(page.getByLabel('Plugin version Required')).toContainText( + 'v0.17.0+ent (pinned) v0.16.0+ent v0.18.0+ent' + ); + }); + + await test.step('pinned version is selected by default with no warning', async () => { + await expect(page.getByLabel('Version differs from pinned')).not.toBeVisible(); + }); + + await test.step('selecting a non-pinned version shows a warning', async () => { + await page.getByLabel('Plugin version Required').selectOption('v0.16.0+ent'); + await expect(page.getByLabel('Version differs from pinned')).toContainText( + 'You have selected v0.16.0+ent, but version v0.17.0+ent is pinned for this plugin. Enabling the engine with this version will override the pinned version for this mount.' + ); + }); + + await test.step('re-selecting the pinned version clears the warning', async () => { + await page.getByLabel('Plugin version Required').selectOption('v0.17.0+ent'); + await expect(page.getByLabel('Version differs from pinned')).not.toBeVisible(); + }); + + await test.step('enabling engine shows error with external plugin name', async () => { + await page.getByRole('button', { name: 'Enable engine' }).click(); + await expect(page.getByLabel('Error')).toContainText( + 'plugin not found in the catalog: vault-plugin-secrets-keymgmt' + ); + }); +}); diff --git a/ui/e2e/tests/superuser/keymgmt-tune-external.ent.spec.ts b/ui/e2e/tests/superuser/keymgmt-tune-external.ent.spec.ts new file mode 100644 index 0000000000..6acdd0ac4e --- /dev/null +++ b/ui/e2e/tests/superuser/keymgmt-tune-external.ent.spec.ts @@ -0,0 +1,204 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { expect, test } from '@playwright/test'; + +const PINNED_PLUGIN_DATA = { + data: { + name: 'vault-plugin-secrets-keymgmt', + type: 'secret', + version: 'v0.17.0+ent', + }, +}; + +const PLUGIN_CATALOG_DATA = { + request_id: 'request_id', + lease_id: '', + renewable: false, + lease_duration: 0, + data: { + detailed: [ + { + builtin: true, + deprecation_status: 'supported', + name: 'keymgmt', + type: 'secret', + version: 'v0.18.1+builtin', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: '', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: 'v0.16.0+ent', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: 'v0.17.0+ent', + }, + { + builtin: false, + name: 'vault-plugin-secrets-keymgmt', + sha256: 'sha256', + type: 'secret', + version: 'v0.18.0+ent', + }, + ], + }, +}; + +const KEYMGMT_EXTERNAL_MOUNT_DATA = { + request_id: 'request_id', + lease_id: '', + renewable: false, + lease_duration: 0, + data: { + accessor: 'vault-plugin-secrets-keymgmt_accessor', + config: { + default_lease_ttl: 2764800, + force_no_cache: false, + listing_visibility: 'hidden', + max_lease_ttl: 2764800, + }, + description: '', + external_entropy_access: false, + local: false, + options: {}, + path: 'keymgmt-external/', + plugin_version: 'v0.17.0+ent', + running_plugin_version: 'v0.17.0+ent', + running_sha256: 'sha256', + seal_wrap: false, + type: 'vault-plugin-secrets-keymgmt', + uuid: 'uuid', + }, + wrap_info: null, + warnings: null, + auth: null, + mount_type: '', +}; + +const UPDATED_KEYMGMT_EXTERNAL_MOUNT_DATA = { + ...KEYMGMT_EXTERNAL_MOUNT_DATA, + data: { + ...KEYMGMT_EXTERNAL_MOUNT_DATA.data, + plugin_version: 'v0.18.0+ent', + running_plugin_version: 'v0.18.0+ent', + }, +}; + +test('tune external keymgmt workflow', async ({ page }) => { + await test.step('mock the keymgmt pinned version response', async () => { + await page.route('**v1/sys/plugins/pins/secret/vault-plugin-secrets-keymgmt', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(PINNED_PLUGIN_DATA), + }); + } else { + await route.continue(); + } + }); + }); + + await test.step('mock the plugin catalog response to return builtin and external keymgmt plugins', async () => { + await page.route('**v1/sys/plugins/catalog', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(PLUGIN_CATALOG_DATA), + }); + } else { + await route.continue(); + } + }); + }); + + await test.step('mock the keymgmt external mount response', async () => { + await page.route('**/v1/sys/internal/ui/mounts/keymgmt-external', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(KEYMGMT_EXTERNAL_MOUNT_DATA), + }); + } else { + await route.continue(); + } + }); + }); + + await test.step('mock the initial keymgmt external tunde response', async () => { + await page.route('**/v1/sys/mounts/keymgmt-external/tune', async (route) => { + if (route.request().method() === 'POST') { + await route.fulfill({ + status: 204, + contentType: 'application/json', + }); + } else { + await route.continue(); + } + }); + }); + + await page.goto('dashboard'); + + await test.step("navigate to the external keymgmt mount's general settings page", async () => { + await page.goto('secrets-engines/keymgmt-external/list'); + await page.getByRole('button', { name: 'Manage' }).click(); + await page.getByRole('link', { name: 'Configure' }).click(); + await expect(page.getByRole('heading', { level: 1 })).toContainText('keymgmt-external configuration'); + await expect(page.getByRole('link', { name: 'General settings' })).toBeVisible(); + await expect(page.getByRole('paragraph').nth(2)).toContainText('vault-plugin-secrets-keymgmt'); + await expect(page.getByRole('paragraph').nth(3)).toContainText('v0.17.0+ent (Pinned)'); + }); + + await test.step('verify that selecting an unpinned version shows the override message', async () => { + await page.getByLabel('Update version to:').selectOption('v0.16.0+ent'); + await expect(page.getByLabel('Override pinned version')).toContainText( + 'You have selected v0.16.0+ent, but version v0.17.0+ent is pinned for this plugin. Updating to this version will override the pinned version for this mount.' + ); + }); + + await test.step('reset the version selection and verify that the override message goes away', async () => { + await page.getByLabel('Update version to:').selectOption(''); + await expect(page.getByLabel('Override pinned version')).not.toBeVisible(); + }); + + await test.step('verify that selecting an unpinned version shows the override message', async () => { + await page.getByLabel('Update version to:').selectOption('v0.18.0+ent'); + await expect(page.getByLabel('Override pinned version')).toContainText( + 'You have selected v0.18.0+ent, but version v0.17.0+ent is pinned for this plugin. Updating to this version will override the pinned version for this mount.' + ); + }); + + await test.step('mock updated mount response after tuning with a new plugin version', async () => { + await page.route('**/v1/sys/internal/ui/mounts/keymgmt-external', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(UPDATED_KEYMGMT_EXTERNAL_MOUNT_DATA), + }); + } else { + await route.continue(); + } + }); + }); + + await page.getByRole('button', { name: 'Save changes' }).click(); +}); diff --git a/ui/e2e/tests/superuser/keymgmt.spec.ts b/ui/e2e/tests/superuser/keymgmt.spec.ts new file mode 100644 index 0000000000..4b8371f1bd --- /dev/null +++ b/ui/e2e/tests/superuser/keymgmt.spec.ts @@ -0,0 +1,86 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { expect, test } from '@playwright/test'; + +test('keymgmt workflow', async ({ page }) => { + await test.step('mock the distribution response', async () => { + await page.route('**/v1/keymgmt-builtin/kms/test-provider/key/test-key', async (route) => { + if (route.request().method() === 'PUT') { + await route.fulfill({ + status: 200, + contentType: 'application/json', + }); + } else { + await route.continue(); + } + }); + }); + + await page.goto('dashboard'); + + await test.step('enable Key Management secrets engine', async () => { + await page.getByRole('link', { name: 'Secrets', exact: true }).click(); + await page.getByRole('link', { name: 'Enable new engine' }).click(); + await page.getByLabel('Key Management - enabled').click(); + await page.getByRole('textbox', { name: 'Path' }).fill('keymgmt-builtin'); + await page.getByRole('button', { name: 'Method Options' }).click(); + await page.getByRole('textbox', { name: 'Description' }).fill('This is a keymgmt mount.'); + await page.getByRole('checkbox', { name: 'Local' }).check(); + await page.getByRole('button', { name: 'Enable engine' }).click(); + + await expect(page.getByText('Success', { exact: true })).toBeVisible(); + await expect( + page.getByText('Successfully mounted the keymgmt secrets engine at keymgmt-builtin') + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + }); + + await test.step('create a provider', async () => { + await page.getByRole('link', { name: 'Create provider' }).click(); + await page.getByLabel('Type').selectOption('azurekeyvault'); + await page.getByRole('textbox', { name: 'Provider name' }).fill('test-provider'); + await page.getByRole('textbox', { name: 'Key Vault instance name' }).fill('keyvault-name'); + await page.getByRole('textbox', { name: 'client_id' }).fill('a0454cd1-e28e-405e-bc50-7477fa8a00b7'); + await page.getByRole('textbox', { name: 'client_secret' }).fill('eR%HizuCVEpAKgeaUEx'); + await page.getByRole('textbox', { name: 'tenant_id' }).fill('cd4bf224-d114-4f96-9bbc-b8f45751c43f'); + await page.getByRole('button', { name: 'Create provider' }).click(); + + await expect(page.locator('span').filter({ hasText: 'test-provider' })).toBeVisible(); + await expect(page.getByText('Azure Key Vault')).toBeVisible(); + await expect(page.getByText('keyvault-name')).toBeVisible(); + await expect(page.getByText('None')).toBeVisible(); + }); + + await test.step('create a key', async () => { + await page.getByRole('link', { name: 'Keys' }).click(); + await page.getByRole('link', { name: 'Create key' }).click(); + await page.getByRole('textbox', { name: 'Key name' }).fill('test-key'); + await page.getByRole('checkbox', { name: 'Allow deletion' }).check(); + await page.getByRole('button', { name: 'Create key' }).click(); + + await expect(page.locator('section')).toContainText('test-key'); + await expect(page.locator('section')).toContainText('rsa-2048'); + await expect(page.locator('section')).toContainText('Yes'); + }); + + await test.step('distribute key to provider', async () => { + await page.getByLabel('toolbar actions').getByRole('button', { name: 'Distribute key' }).click(); + await page.getByText('Search').click(); + await page.getByRole('option', { name: 'test-provider' }).click(); + await page.getByText('Encrypt').click(); + await page.getByText('Decrypt').click(); + await page.getByText('Sign').click(); + await page.getByText('Verify').click(); + await page.getByText('Wrap', { exact: true }).click(); + await page.getByText('Unwrap').click(); + await page.getByRole('radio', { name: 'HSM' }).check(); + await page.getByRole('button', { name: 'Distribute key' }).click(); + + await expect(page.getByText('Success', { exact: true })).toBeVisible(); + await expect(page.getByText('Successfully distributed key test-key to test-provider')).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + }); +});