diff --git a/ui/admin/tests/e2e/helpers/boundary-ui.js b/ui/admin/tests/e2e/helpers/boundary-ui.js index d908f89b7..6dc308d0a 100644 --- a/ui/admin/tests/e2e/helpers/boundary-ui.js +++ b/ui/admin/tests/e2e/helpers/boundary-ui.js @@ -231,7 +231,8 @@ exports.createVaultCredentialStore = async (page, vaultAddr, clientToken) => { }; /** - * Uses the UI to create a static key pair credential . Assumes you have selected the desired project. + * Uses the UI to create a static key pair credential. + * Assumes you have selected the desired project. * @param {Page} page Playwright page object * @param {string} username username for the credential * @param {string} keyPath path to the private key @@ -240,7 +241,17 @@ exports.createVaultCredentialStore = async (page, vaultAddr, clientToken) => { exports.createStaticCredentialKeyPair = async (page, username, keyPath) => { const credentialName = 'Credential ' + nanoid(); await page.getByRole('link', { name: 'Credentials', exact: true }).click(); - await page.getByRole('link', { name: 'New', exact: true }).click(); + + const newButtonIsVisible = await page + .getByRole('link', { name: 'New', exact: true }) + .isVisible(); + if (newButtonIsVisible) { + await page.getByRole('link', { name: 'New', exact: true }).click(); + } else { + await page.getByText('Manage').click(); + await page.getByRole('link', { name: 'New Credential' }).click(); + } + await page.getByLabel('Name (Optional)').fill(credentialName); await page.getByLabel('Description').fill('This is an automated test'); await page @@ -266,6 +277,61 @@ exports.createStaticCredentialKeyPair = async (page, username, keyPath) => { return credentialName; }; +/** + * Uses the UI to create a static username and password credential. + * Assumes you have selected the desired project. + * @param {Page} page Playwright page object + * @param {string} username username for the credential + * @param {string} password password for the credential + * @returns Name of the credential + */ +exports.createStaticCredentialUsernamePassword = async ( + page, + username, + password, +) => { + const credentialsBreadcrumbIsVisible = await page + .getByRole('breadcrumbs', { name: 'Credentials' }) + .isVisible(); + if (credentialsBreadcrumbIsVisible) { + await page.getByRole('breadcrumbs', { name: 'Credentials' }).click(); + } else { + await page.getByRole('link', { name: 'Credentials', exact: true }).click(); + } + + const newButtonIsVisible = await page + .getByRole('link', { name: 'New', exact: true }) + .isVisible(); + if (newButtonIsVisible) { + await page.getByRole('link', { name: 'New', exact: true }).click(); + } else { + await page.getByText('Manage').click(); + await page.getByRole('link', { name: 'New Credential' }).click(); + } + + const credentialName = 'Credential ' + nanoid(); + await page.getByLabel('Name (Optional)').fill(credentialName); + await page.getByLabel('Description').fill('This is an automated test'); + await page + .getByRole('group', { name: 'Type' }) + .getByLabel('Username & Password') + .click(); + await page.getByLabel('Username', { exact: true }).fill(username); + await page.getByLabel('Password', { exact: true }).fill(password); + await page.getByRole('button', { name: 'Save' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(credentialName), + ).toBeVisible(); + + return credentialName; +}; + /** * Uses the UI to create a vault-generic credential library. Assumes you have selected * the desired credential store. @@ -684,10 +750,22 @@ exports.addBrokeredCredentialsToTarget = async ( await page .getByRole('link', { name: 'Brokered Credentials', exact: true }) .click(); - await page + + const addBrokeredCredentialsButtonIsVisible = await page .getByRole('article') .getByRole('link', { name: 'Add Brokered Credentials', exact: true }) - .click(); + .isVisible(); + + if (addBrokeredCredentialsButtonIsVisible) { + await page + .getByRole('article') + .getByRole('link', { name: 'Add Brokered Credentials', exact: true }) + .click(); + } else { + await page.getByText('Manage').click(); + await page.getByRole('link', { name: 'Add Brokered Credentials' }).click(); + } + await page .getByRole('cell', { name: credentialName }) .locator('..') @@ -725,13 +803,28 @@ exports.addInjectedCredentialsToTarget = async ( exact: true, }) .click(); - await page + + const addInjectedCredentialsButtonIsVisible = await page .getByRole('article') .getByRole('link', { name: 'Add Injected Application Credentials', exact: true, }) - .click(); + .isVisible(); + + if (addInjectedCredentialsButtonIsVisible) { + await page + .getByRole('article') + .getByRole('link', { + name: 'Add Injected Application Credentials', + exact: true, + }) + .click(); + } else { + await page.getByText('Manage').click(); + await page.getByRole('link', { name: 'Add Injected Application' }).click(); + } + await page .getByRole('cell', { name: credentialName }) .locator('..') diff --git a/ui/admin/tests/e2e/tests/credential-store-static-ent.spec.js b/ui/admin/tests/e2e/tests/credential-store-static-ent.spec.js new file mode 100644 index 000000000..a1c3228cb --- /dev/null +++ b/ui/admin/tests/e2e/tests/credential-store-static-ent.spec.js @@ -0,0 +1,110 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +/* eslint-disable no-undef */ +const { test, expect } = require('@playwright/test'); +const { execSync } = require('child_process'); +const { checkEnv, authenticatedState } = require('../helpers/general'); +const { + authenticateBoundaryCli, + checkBoundaryCli, + deleteOrgCli, +} = require('../helpers/boundary-cli'); +const { + addBrokeredCredentialsToTarget, + addInjectedCredentialsToTarget, + createOrg, + createProject, + createSshTargetWithAddressEnt, + createStaticCredentialKeyPair, + createStaticCredentialStore, + createStaticCredentialUsernamePassword, +} = require('../helpers/boundary-ui'); + +test.use({ storageState: authenticatedState }); + +test.beforeAll(async () => { + await checkEnv([ + 'E2E_SSH_USER', + 'E2E_SSH_KEY_PATH', + 'E2E_TARGET_ADDRESS', + 'E2E_TARGET_PORT', + ]); + + await checkBoundaryCli(); +}); + +test('Multiple Credential Stores (ENT) @ent @aws @docker', async ({ page }) => { + let orgName; + try { + await page.goto('/'); + + orgName = await createOrg(page); + await createProject(page); + const targetName = await createSshTargetWithAddressEnt( + page, + process.env.E2E_TARGET_ADDRESS, + process.env.E2E_TARGET_PORT, + ); + await createStaticCredentialStore(page); + + const credentialName = await createStaticCredentialKeyPair( + page, + process.env.E2E_SSH_USER, + process.env.E2E_SSH_KEY_PATH, + ); + const credentialName2 = await createStaticCredentialUsernamePassword( + page, + process.env.E2E_SSH_USER, + 'testPassword', + ); + + await addBrokeredCredentialsToTarget(page, targetName, credentialName); + await addBrokeredCredentialsToTarget(page, targetName, credentialName2); + + // Remove a credential from the target + await page + .getByRole('link', { name: credentialName2 }) + .locator('..') + .locator('..') + .getByRole('button', { name: 'Manage' }) + .click(); + await page.getByRole('button', { name: 'Remove' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + + await addInjectedCredentialsToTarget(page, targetName, credentialName); + await addInjectedCredentialsToTarget(page, targetName, credentialName2); + + // Remove a credential from the target + await page + .getByRole('link', { name: credentialName2 }) + .locator('..') + .locator('..') + .getByRole('button', { name: 'Manage' }) + .click(); + await page.getByRole('button', { name: 'Remove' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + } finally { + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, + ); + const orgs = JSON.parse(execSync('boundary scopes list -format json')); + const org = orgs.items.filter((obj) => obj.name == orgName)[0]; + if (org) { + await deleteOrgCli(org.id); + } + } +}); diff --git a/ui/admin/tests/e2e/tests/credential-store-static.spec.js b/ui/admin/tests/e2e/tests/credential-store-static.spec.js index 292750ce4..c45f18d8c 100644 --- a/ui/admin/tests/e2e/tests/credential-store-static.spec.js +++ b/ui/admin/tests/e2e/tests/credential-store-static.spec.js @@ -5,21 +5,24 @@ /* eslint-disable no-undef */ const { test, expect } = require('@playwright/test'); +const { execSync } = require('child_process'); const { readFile } = require('fs/promises'); const { nanoid } = require('nanoid'); const { checkEnv, authenticatedState } = require('../helpers/general'); const { authenticateBoundaryCli, checkBoundaryCli, + deleteOrgCli, getSessionCli, } = require('../helpers/boundary-cli'); const { + addBrokeredCredentialsToTarget, createOrg, createProject, createTargetWithAddress, createStaticCredentialStore, createStaticCredentialKeyPair, - addBrokeredCredentialsToTarget, + createStaticCredentialUsernamePassword, } = require('../helpers/boundary-ui'); test.use({ storageState: authenticatedState }); @@ -55,160 +58,227 @@ test.beforeEach(async ({ page }) => { test('Static Credential Store (User & Key Pair) @ce @aws @docker', async ({ page, }) => { - const credentialName = await createStaticCredentialKeyPair( - page, - process.env.E2E_SSH_USER, - process.env.E2E_SSH_KEY_PATH, - ); - await addBrokeredCredentialsToTarget(page, targetName, credentialName); - - await authenticateBoundaryCli( - process.env.BOUNDARY_ADDR, - process.env.E2E_PASSWORD_AUTH_METHOD_ID, - process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, - process.env.E2E_PASSWORD_ADMIN_PASSWORD, - ); - const session = await getSessionCli(orgName, projectName, targetName); - const retrievedUser = session.item.credentials[0].credential.username; - const retrievedKey = session.item.credentials[0].credential.private_key; - - if (process.env.E2E_SSH_USER != retrievedUser) { - throw new Error( - 'Stored User does not match. EXPECTED: ' + - process.env.E2E_SSH_USER + - ', ACTUAL: ' + - retrievedUser, + try { + const credentialName = await createStaticCredentialKeyPair( + page, + process.env.E2E_SSH_USER, + process.env.E2E_SSH_KEY_PATH, ); - } + await addBrokeredCredentialsToTarget(page, targetName, credentialName); - const keyData = await readFile(process.env.E2E_SSH_KEY_PATH, { - encoding: 'utf-8', - }); - if (keyData != retrievedKey) { - throw new Error('Stored Key does not match'); + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, + ); + const session = await getSessionCli(orgName, projectName, targetName); + const retrievedUser = session.item.credentials[0].credential.username; + const retrievedKey = session.item.credentials[0].credential.private_key; + + if (process.env.E2E_SSH_USER != retrievedUser) { + throw new Error( + 'Stored User does not match. EXPECTED: ' + + process.env.E2E_SSH_USER + + ', ACTUAL: ' + + retrievedUser, + ); + } + + const keyData = await readFile(process.env.E2E_SSH_KEY_PATH, { + encoding: 'utf-8', + }); + if (keyData != retrievedKey) { + throw new Error('Stored Key does not match'); + } + } finally { + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, + ); + const orgs = JSON.parse(execSync('boundary scopes list -format json')); + const org = orgs.items.filter((obj) => obj.name == orgName)[0]; + if (org) { + await deleteOrgCli(org.id); + } } }); test('Static Credential Store (Username & Password) @ce @aws @docker', async ({ page, }) => { - const credentialName = 'Credential ' + nanoid(); - await page.getByRole('link', { name: 'Credentials', exact: true }).click(); - await page.getByRole('link', { name: 'New', exact: true }).click(); - await page.getByLabel('Name (Optional)').fill(credentialName); - await page.getByLabel('Description').fill('This is an automated test'); - await page - .getByRole('group', { name: 'Type' }) - .getByLabel('Username & Password') - .click(); - await page - .getByLabel('Username', { exact: true }) - .fill(process.env.E2E_SSH_USER); - const testPassword = 'password'; - await page.getByLabel('Password', { exact: true }).fill(testPassword); - await page.getByRole('button', { name: 'Save' }).click(); - await expect( - page.getByRole('alert').getByText('Success', { exact: true }), - ).toBeVisible(); - await page.getByRole('button', { name: 'Dismiss' }).click(); - await expect( - page - .getByRole('navigation', { name: 'breadcrumbs' }) - .getByText(credentialName), - ).toBeVisible(); - - await addBrokeredCredentialsToTarget(page, targetName, credentialName); - - await authenticateBoundaryCli( - process.env.BOUNDARY_ADDR, - process.env.E2E_PASSWORD_AUTH_METHOD_ID, - process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, - process.env.E2E_PASSWORD_ADMIN_PASSWORD, - ); - const session = await getSessionCli(orgName, projectName, targetName); - const retrievedUser = session.item.credentials[0].credential.username; - const retrievedPassword = session.item.credentials[0].credential.password; - - if (process.env.E2E_SSH_USER != retrievedUser) { - throw new Error( - 'Stored User does not match. EXPECTED: ' + - process.env.E2E_SSH_USER + - ', ACTUAL: ' + - retrievedUser, + try { + const testPassword = 'password'; + const credentialName = await createStaticCredentialUsernamePassword( + page, + process.env.E2E_SSH_USER, + testPassword, ); - } - if (testPassword != retrievedPassword) { - throw new Error( - 'Stored Password does not match. EXPECTED: ' + - testPassword + - ', ACTUAL: ' + - retrievedPassword, + await addBrokeredCredentialsToTarget(page, targetName, credentialName); + + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, ); + const session = await getSessionCli(orgName, projectName, targetName); + const retrievedUser = session.item.credentials[0].credential.username; + const retrievedPassword = session.item.credentials[0].credential.password; + + if (process.env.E2E_SSH_USER != retrievedUser) { + throw new Error( + 'Stored User does not match. EXPECTED: ' + + process.env.E2E_SSH_USER + + ', ACTUAL: ' + + retrievedUser, + ); + } + if (testPassword != retrievedPassword) { + throw new Error( + 'Stored Password does not match. EXPECTED: ' + + testPassword + + ', ACTUAL: ' + + retrievedPassword, + ); + } + } finally { + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, + ); + const orgs = JSON.parse(execSync('boundary scopes list -format json')); + const org = orgs.items.filter((obj) => obj.name == orgName)[0]; + if (org) { + await deleteOrgCli(org.id); + } } }); test('Static Credential Store (JSON) @ce @aws @docker', async ({ page }) => { - const credentialName = 'Credential ' + nanoid(); - await page.getByRole('link', { name: 'Credentials', exact: true }).click(); - await page.getByRole('link', { name: 'New', exact: true }).click(); - await page.getByLabel('Name (Optional)').fill(credentialName); - await page.getByLabel('Description').fill('This is an automated test'); - await page.getByRole('group', { name: 'Type' }).getByLabel('JSON').click(); - await page.getByText('{}').click(); - const testName = 'name-json'; - const testPassword = 'password-json'; - const testId = 'id-json'; - await page.keyboard.type( - `"username": "${testName}", + try { + const credentialName = 'Credential ' + nanoid(); + await page.getByRole('link', { name: 'Credentials', exact: true }).click(); + await page.getByRole('link', { name: 'New', exact: true }).click(); + await page.getByLabel('Name (Optional)').fill(credentialName); + await page.getByLabel('Description').fill('This is an automated test'); + await page.getByRole('group', { name: 'Type' }).getByLabel('JSON').click(); + await page.getByText('{}').click(); + const testName = 'name-json'; + const testPassword = 'password-json'; + const testId = 'id-json'; + await page.keyboard.type( + `"username": "${testName}", "password": "${testPassword}", "id": "${testId}"`, - ); - await page.getByRole('button', { name: 'Save' }).click(); - await expect( - page.getByRole('alert').getByText('Success', { exact: true }), - ).toBeVisible(); - await page.getByRole('button', { name: 'Dismiss' }).click(); - await expect( - page - .getByRole('navigation', { name: 'breadcrumbs' }) - .getByText(credentialName), - ).toBeVisible(); - - await addBrokeredCredentialsToTarget(page, targetName, credentialName); - - await authenticateBoundaryCli( - process.env.BOUNDARY_ADDR, - process.env.E2E_PASSWORD_AUTH_METHOD_ID, - process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, - process.env.E2E_PASSWORD_ADMIN_PASSWORD, - ); - const session = await getSessionCli(orgName, projectName, targetName); - const retrievedUser = session.item.credentials[0].credential.username; - const retrievedPassword = session.item.credentials[0].credential.password; - const retrievedId = session.item.credentials[0].credential.id; - - if (testName != retrievedUser) { - throw new Error( - 'Stored User does not match. EXPECTED: ' + - process.env.E2E_SSH_USER + - ', ACTUAL: ' + - retrievedUser, ); - } - if (testPassword != retrievedPassword) { - throw new Error( - 'Stored Password does not match. EXPECTED: ' + - testPassword + - ', ACTUAL: ' + - retrievedPassword, + await page.getByRole('button', { name: 'Save' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(credentialName), + ).toBeVisible(); + + await addBrokeredCredentialsToTarget(page, targetName, credentialName); + + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, ); - } - if (testId != retrievedId) { - throw new Error( - 'Stored ID does not match. EXPECTED: ' + - testId + - ', ACTUAL: ' + - retrievedId, + const session = await getSessionCli(orgName, projectName, targetName); + const retrievedUser = session.item.credentials[0].credential.username; + const retrievedPassword = session.item.credentials[0].credential.password; + const retrievedId = session.item.credentials[0].credential.id; + + if (testName != retrievedUser) { + throw new Error( + 'Stored User does not match. EXPECTED: ' + + process.env.E2E_SSH_USER + + ', ACTUAL: ' + + retrievedUser, + ); + } + if (testPassword != retrievedPassword) { + throw new Error( + 'Stored Password does not match. EXPECTED: ' + + testPassword + + ', ACTUAL: ' + + retrievedPassword, + ); + } + if (testId != retrievedId) { + throw new Error( + 'Stored ID does not match. EXPECTED: ' + + testId + + ', ACTUAL: ' + + retrievedId, + ); + } + } finally { + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, ); + const orgs = JSON.parse(execSync('boundary scopes list -format json')); + const org = orgs.items.filter((obj) => obj.name == orgName)[0]; + if (org) { + await deleteOrgCli(org.id); + } + } +}); + +test('Multiple Credential Stores (CE) @ce @aws @docker', async ({ page }) => { + try { + const credentialName = await createStaticCredentialKeyPair( + page, + process.env.E2E_SSH_USER, + process.env.E2E_SSH_KEY_PATH, + ); + const credentialName2 = await createStaticCredentialUsernamePassword( + page, + process.env.E2E_SSH_USER, + 'testPassword', + ); + + await addBrokeredCredentialsToTarget(page, targetName, credentialName); + await addBrokeredCredentialsToTarget(page, targetName, credentialName2); + + // Remove the host source from the target + await page + .getByRole('link', { name: credentialName2 }) + .locator('..') + .locator('..') + .getByRole('button', { name: 'Manage' }) + .click(); + await page.getByRole('button', { name: 'Remove' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + } finally { + await authenticateBoundaryCli( + process.env.BOUNDARY_ADDR, + process.env.E2E_PASSWORD_AUTH_METHOD_ID, + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, + ); + const orgs = JSON.parse(execSync('boundary scopes list -format json')); + const org = orgs.items.filter((obj) => obj.name == orgName)[0]; + if (org) { + await deleteOrgCli(org.id); + } } });