/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: MPL-2.0 */ import { currentURL, currentRouteName, settled, fillIn, waitUntil, find } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { create } from 'ember-cli-page-object'; import consoleClass from 'vault/tests/pages/components/console/ui-panel'; import authPage from 'vault/tests/pages/auth'; import scopesPage from 'vault/tests/pages/secrets/backend/kmip/scopes'; import rolesPage from 'vault/tests/pages/secrets/backend/kmip/roles'; import credentialsPage from 'vault/tests/pages/secrets/backend/kmip/credentials'; import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend'; const uiConsole = create(consoleClass); const getRandomPort = () => { let a = Math.floor(100000 + Math.random() * 900000); a = String(a); return a.substring(0, 4); }; const mount = async (shouldConfig = true) => { const now = Date.now(); const path = `kmip-${now}`; const addr = `127.0.0.1:${getRandomPort()}`; // use random port await settled(); const commands = shouldConfig ? [`write sys/mounts/${path} type=kmip`, `write ${path}/config listen_addrs=${addr}`] : [`write sys/mounts/${path} type=kmip`]; await uiConsole.runCommands(commands); await settled(); const res = uiConsole.lastLogOutput; if (res.includes('Error')) { throw new Error(`Error mounting secrets engine: ${res}`); } return path; }; const createScope = async () => { const path = await mount(); await settled(); const scope = `scope-${Date.now()}`; await settled(); await uiConsole.runCommands([`write ${path}/scope/${scope} -force`]); await settled(); const res = uiConsole.lastLogOutput; if (res.includes('Error')) { throw new Error(`Error creating scope: ${res}`); } return { path, scope }; }; const createRole = async () => { const { path, scope } = await createScope(); await settled(); const role = `role-${Date.now()}`; await uiConsole.runCommands([`write ${path}/scope/${scope}/role/${role} operation_all=true`]); await settled(); const res = uiConsole.lastLogOutput; if (res.includes('Error')) { throw new Error(`Error creating role: ${res}`); } return { path, scope, role }; }; const generateCreds = async () => { const { path, scope, role } = await createRole(); await settled(); await uiConsole.runCommands([ `write ${path}/scope/${scope}/role/${role}/credential/generate format=pem -field=serial_number`, ]); const serial = uiConsole.lastLogOutput; if (serial.includes('Error')) { throw new Error(`Credential generation failed with error: ${serial}`); } return { path, scope, role, serial }; }; module('Acceptance | Enterprise | KMIP secrets', function (hooks) { setupApplicationTest(hooks); hooks.beforeEach(async function () { await authPage.login(); return; }); test('it enables KMIP secrets engine', async function (assert) { const path = `kmip-${Date.now()}`; await mountSecrets.enable('kmip', path); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes`, 'mounts and redirects to the kmip scopes page' ); assert.ok(scopesPage.isEmpty, 'renders empty state'); }); test('it can configure a KMIP secrets engine', async function (assert) { const path = await mount(false); await scopesPage.visit({ backend: path }); await settled(); await scopesPage.configurationLink(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/configuration`, 'configuration navigates to the config page' ); assert.ok(scopesPage.isEmpty, 'config page renders empty state'); await scopesPage.configureLink(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/configure`, 'configuration navigates to the configure page' ); const addr = `127.0.0.1:${getRandomPort()}`; await fillIn('[data-test-string-list-input="0"]', addr); await scopesPage.submit(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/configuration`, 'redirects to configuration page after saving config' ); assert.notOk(scopesPage.isEmpty, 'configuration page no longer renders empty state'); }); test('it can revoke from the credentials show page', async function (assert) { const { path, scope, role, serial } = await generateCreds(); await settled(); await credentialsPage.visitDetail({ backend: path, scope, role, serial }); await settled(); await waitUntil(() => find('[data-test-confirm-action-trigger]')); assert.dom('[data-test-confirm-action-trigger]').exists('delete button exists'); await credentialsPage.delete().confirmDelete(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials`, 'redirects to the credentials list' ); assert.ok(credentialsPage.isEmpty, 'renders an empty credentials page'); }); test('it can create a scope', async function (assert) { const path = await mount(this); await scopesPage.visit({ backend: path }); await settled(); await scopesPage.createLink(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/create`, 'navigates to the kmip scope create page' ); // create scope await scopesPage.scopeName('foo'); await settled(); await scopesPage.submit(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes`, 'navigates to the kmip scopes page after create' ); assert.strictEqual(scopesPage.listItemLinks.length, 1, 'renders a single scope'); }); test('it can delete a scope from the list', async function (assert) { const { path } = await createScope(); await scopesPage.visit({ backend: path }); await settled(); // delete the scope await scopesPage.listItemLinks.objectAt(0).menuToggle(); await settled(); await scopesPage.delete(); await settled(); await scopesPage.confirmDelete(); await settled(); assert.strictEqual(scopesPage.listItemLinks.length, 0, 'no scopes'); assert.ok(scopesPage.isEmpty, 'renders the empty state'); }); test('it can create a role', async function (assert) { // moving create scope here to help with flaky test const path = await mount(); await settled(); const scope = `scope-for-can-create-role`; await settled(); await uiConsole.runCommands([`write ${path}/scope/${scope} -force`]); await settled(); const res = uiConsole.lastLogOutput; if (res.includes('Error')) { throw new Error(`Error creating scope: ${res}`); } const role = `role-new-role`; await rolesPage.visit({ backend: path, scope }); await settled(); assert.ok(rolesPage.isEmpty, 'renders the empty role page'); await rolesPage.create(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/${scope}/roles/create`, 'links to the role create form' ); await rolesPage.roleName(role); await settled(); await rolesPage.submit(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/${scope}/roles`, 'redirects to roles list' ); assert.strictEqual(rolesPage.listItemLinks.length, 1, 'renders a single role'); }); test('it can delete a role from the list', async function (assert) { const { path, scope } = await createRole(); await rolesPage.visit({ backend: path, scope }); await settled(); // delete the role await rolesPage.listItemLinks.objectAt(0).menuToggle(); await settled(); await rolesPage.delete(); await settled(); await rolesPage.confirmDelete(); await settled(); assert.strictEqual(rolesPage.listItemLinks.length, 0, 'renders no roles'); assert.ok(rolesPage.isEmpty, 'renders empty'); }); test('it can delete a role from the detail page', async function (assert) { const { path, scope, role } = await createRole(); await settled(); await rolesPage.visitDetail({ backend: path, scope, role }); await settled(); await waitUntil(() => find('[data-test-kmip-link-edit-role]')); await rolesPage.detailEditLink(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/edit`, 'navigates to role edit' ); await rolesPage.cancelLink(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}`, 'cancel navigates to role show' ); await rolesPage.delete().confirmDelete(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/${scope}/roles`, 'redirects to the roles list' ); assert.ok(rolesPage.isEmpty, 'renders an empty roles page'); }); test('it can create a credential', async function (assert) { // TODO come back and figure out why issue here with test const { path, scope, role } = await createRole(); await credentialsPage.visit({ backend: path, scope, role }); await settled(); assert.ok(credentialsPage.isEmpty, 'renders empty creds page'); await credentialsPage.generateCredentialsLink(); await settled(); assert.strictEqual( currentURL(), `/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials/generate`, 'navigates to generate credentials' ); await credentialsPage.submit(); await settled(); assert.strictEqual( currentRouteName(), 'vault.cluster.secrets.backend.kmip.credentials.show', 'generate redirects to the show page' ); await credentialsPage.backToRoleLink(); await settled(); assert.strictEqual(credentialsPage.listItemLinks.length, 1, 'renders a single credential'); }); test('it can revoke a credential from the list', async function (assert) { const { path, scope, role } = await generateCreds(); await credentialsPage.visit({ backend: path, scope, role }); // revoke the credentials await settled(); await credentialsPage.listItemLinks.objectAt(0).menuToggle(); await settled(); await credentialsPage.delete().confirmDelete(); await settled(); assert.strictEqual(credentialsPage.listItemLinks.length, 0, 'renders no credentials'); assert.ok(credentialsPage.isEmpty, 'renders empty'); }); });