test(e2e): Add additional assertions to UI tests (#2634)
* test(e2e): Address deprecation * test(e2e): Add additional connection assertions on tests * CR: Fix flake when waiting for sessions If no sessions are available and the page needs to reload, we should wait to ensure that the page has loaded before checking what the state of the sessions are * CR: Address flake in connect methods Noticed an issue where a session recording was not playable. The theory is that the session didn't last long enough before it was cancelled. This adds a 2 second wait after establishing a connection before continuing. * CR: Use authenticated state by default * CR: Update dynamic host catalog assertion * CR: Simplify host set checks - Use .all() when looping - Add check to see if we need to reload the page or not * CR: Be more specific with locator Ran into a conflict with the Static/Dynamic host catalog type and the Static/Dynamic credential type. * CR: Update connection methods to be more resilient Rather than using a hardcoded sleep, we will now run a command on the target and wait for a specific string to return before continuing. * CR
This commit is contained in:
1 parent
faf4ecfac6
commit
1cbf3b227e
29 files changed
+632
-169
No files matched your search
@@ -37,7 +37,7 @@ export class HostCatalogsPage extends BaseResourcePage {
|
||||
await this.page.getByLabel('Name').fill(hostCatalogName);
|
||||
await this.page.getByLabel('Description').fill('This is an automated test');
|
||||
await this.page
|
||||
.getByRole('group', { name: 'Type' })
|
||||
.getByRole('group', { name: 'Type', exact: 'true' })
|
||||
.getByLabel('Static')
|
||||
.click();
|
||||
await this.page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
@@ -17,15 +17,16 @@ export class SessionsPage extends BasePage {
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('link', { name: 'Sessions' })
|
||||
.click();
|
||||
await expect(
|
||||
this.page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText('Sessions'),
|
||||
).toBeVisible();
|
||||
let i = 0;
|
||||
let sessionIsVisible = false;
|
||||
let sessionIsActive = false;
|
||||
do {
|
||||
await expect(
|
||||
this.page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText('Sessions'),
|
||||
).toBeVisible();
|
||||
|
||||
i = i + 1;
|
||||
sessionIsVisible = await this.page
|
||||
.getByRole('cell', { name: targetName })
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import { authenticatedState } from '../global-setup';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
export default defineConfig({
|
||||
@@ -13,12 +14,13 @@ export default defineConfig({
|
||||
workers: 1, // Tests need to be run in serial, otherwise there may be conflicts when using the CLI
|
||||
use: {
|
||||
baseURL: process.env.BOUNDARY_ADDR,
|
||||
trace: 'retain-on-failure',
|
||||
screenshot: 'only-on-failure',
|
||||
extraHTTPHeaders: {
|
||||
// This token is set in global-setup.js
|
||||
Authorization: `Bearer ${process.env.E2E_TOKEN}`,
|
||||
},
|
||||
screenshot: 'only-on-failure',
|
||||
storageState: authenticatedState,
|
||||
trace: 'retain-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
@@ -12,10 +12,9 @@ import { AliasesPage } from '../pages/aliases.js';
|
||||
import { CredentialStoresPage } from '../pages/credential-stores.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
@@ -35,6 +34,7 @@ test.describe('Aliases (Enterprise)', async () => {
|
||||
await page.goto('/');
|
||||
let orgName;
|
||||
let alias;
|
||||
let connect;
|
||||
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10);
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
@@ -83,9 +83,16 @@ test.describe('Aliases (Enterprise)', async () => {
|
||||
adminLoginName,
|
||||
adminPassword,
|
||||
);
|
||||
await boundaryCli.authorizeSessionByAlias(alias);
|
||||
connect = await boundaryCli.connectSshToAlias(alias);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
|
||||
// Clear destination from alias
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
await page.getByRole('link', { name: alias }).click();
|
||||
// Note: On the Target details page, there is a section with the header
|
||||
// "Aliases". The extra check here is to ensure that we are on the Alias
|
||||
@@ -101,6 +108,9 @@ test.describe('Aliases (Enterprise)', async () => {
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
@@ -133,6 +143,7 @@ test.describe('Aliases (Enterprise)', async () => {
|
||||
await page.goto('/');
|
||||
let orgName;
|
||||
let alias;
|
||||
let connect;
|
||||
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10);
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
@@ -165,8 +176,13 @@ test.describe('Aliases (Enterprise)', async () => {
|
||||
);
|
||||
|
||||
// Connect to target using alias
|
||||
await boundaryCli.authorizeSessionByAlias(alias);
|
||||
connect = await boundaryCli.connectSshToAlias(alias);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
@@ -201,6 +217,7 @@ test.describe('Aliases (Enterprise)', async () => {
|
||||
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10);
|
||||
let orgId;
|
||||
let alias;
|
||||
let connect;
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
const orgName = await orgsPage.createOrg();
|
||||
@@ -243,8 +260,19 @@ test.describe('Aliases (Enterprise)', async () => {
|
||||
alias = 'example.alias.' + nanoid();
|
||||
const aliasesPage = new AliasesPage(page);
|
||||
await aliasesPage.createAliasForTarget(alias, targetId);
|
||||
await boundaryCli.authorizeSessionByAlias(alias);
|
||||
connect = await boundaryCli.connectSshToAlias(alias);
|
||||
await page
|
||||
.getByRole('navigation', { name: 'General' })
|
||||
.getByRole('link', { name: 'Orgs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await page.getByRole('link', { name: projectName }).click();
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
@@ -11,10 +11,9 @@ import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
import { AliasesPage } from '../pages/aliases.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
@@ -28,10 +27,13 @@ test.describe('Aliases', async () => {
|
||||
adminPassword,
|
||||
targetAddress,
|
||||
targetPort,
|
||||
sshUser,
|
||||
sshKeyPath,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
let orgName;
|
||||
let alias;
|
||||
let connect;
|
||||
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10);
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
@@ -39,7 +41,10 @@ test.describe('Aliases', async () => {
|
||||
const projectsPage = new ProjectsPage(page);
|
||||
await projectsPage.createProject();
|
||||
const targetsPage = new TargetsPage(page);
|
||||
await targetsPage.createTargetWithAddress(targetAddress, targetPort);
|
||||
const targetName = await targetsPage.createTargetWithAddress(
|
||||
targetAddress,
|
||||
targetPort,
|
||||
);
|
||||
|
||||
// Create alias for target
|
||||
const aliasName = 'Alias ' + nanoid();
|
||||
@@ -61,9 +66,16 @@ test.describe('Aliases', async () => {
|
||||
adminLoginName,
|
||||
adminPassword,
|
||||
);
|
||||
await boundaryCli.authorizeSessionByAlias(alias);
|
||||
connect = await boundaryCli.connectToAlias(alias, sshUser, sshKeyPath);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
|
||||
// Clear destination from alias
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
await page.getByRole('link', { name: alias }).click();
|
||||
// Note: On the Target details page, there is a section with the header
|
||||
// "Aliases". The extra check here is to ensure that we are on the Alias
|
||||
@@ -79,6 +91,9 @@ test.describe('Aliases', async () => {
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
@@ -105,10 +120,13 @@ test.describe('Aliases', async () => {
|
||||
adminPassword,
|
||||
targetAddress,
|
||||
targetPort,
|
||||
sshUser,
|
||||
sshKeyPath,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
let orgName;
|
||||
let alias;
|
||||
let connect;
|
||||
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10);
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
@@ -123,15 +141,20 @@ test.describe('Aliases', async () => {
|
||||
await projectsPage.createProject();
|
||||
alias = 'example.alias.' + nanoid();
|
||||
const targetsPage = new TargetsPage(page);
|
||||
await targetsPage.createTargetWithAddressAndAlias(
|
||||
const targetName = await targetsPage.createTargetWithAddressAndAlias(
|
||||
targetAddress,
|
||||
targetPort,
|
||||
alias,
|
||||
);
|
||||
|
||||
// Connect to target using alias
|
||||
await boundaryCli.authorizeSessionByAlias(alias);
|
||||
connect = await boundaryCli.connectToAlias(alias, sshUser, sshKeyPath);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
@@ -158,11 +181,14 @@ test.describe('Aliases', async () => {
|
||||
adminPassword,
|
||||
targetAddress,
|
||||
targetPort,
|
||||
sshUser,
|
||||
sshKeyPath,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10);
|
||||
let orgId;
|
||||
let alias;
|
||||
let connect;
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
const orgName = await orgsPage.createOrg();
|
||||
@@ -194,8 +220,18 @@ test.describe('Aliases', async () => {
|
||||
alias = 'example.alias.' + nanoid();
|
||||
const aliasesPage = new AliasesPage(page);
|
||||
await aliasesPage.createAliasForTarget(alias, targetId);
|
||||
await boundaryCli.authorizeSessionByAlias(alias);
|
||||
await page.getByRole('link', { name: 'Orgs', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Orgs' })).toBeVisible();
|
||||
await page.getByRole('link', { name: orgName }).click();
|
||||
await page.getByRole('link', { name: projectName }).click();
|
||||
|
||||
connect = await boundaryCli.connectToAlias(alias, sshUser, sshKeyPath);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
|
||||
@@ -14,6 +14,9 @@ import { OrgsPage } from '../pages/orgs.js';
|
||||
import { RolesPage } from '../pages/roles.js';
|
||||
import { UsersPage } from '../pages/users.js';
|
||||
|
||||
// Reset storage state for this file to avoid being authenticated
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
|
||||
@@ -13,6 +13,9 @@ import { LoginPage } from '../pages/login.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { UsersPage } from '../pages/users.js';
|
||||
|
||||
// Reset storage state for this file to avoid being authenticated
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
import { CredentialStoresPage } from '../pages/credential-stores.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
@@ -36,7 +36,7 @@ test('Multiple Credential Stores (ENT) @ent @aws @docker', async ({
|
||||
const orgsPage = new OrgsPage(page);
|
||||
orgName = await orgsPage.createOrg();
|
||||
const projectsPage = new ProjectsPage(page);
|
||||
await projectsPage.createProject();
|
||||
const projectName = await projectsPage.createProject();
|
||||
const targetsPage = new TargetsPage(page);
|
||||
const targetName = await targetsPage.createSshTargetWithAddressEnt(
|
||||
targetAddress,
|
||||
@@ -100,6 +100,36 @@ test('Multiple Credential Stores (ENT) @ent @aws @docker', async ({
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
|
||||
// Verify credentials
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
adminLoginName,
|
||||
adminPassword,
|
||||
);
|
||||
const orgId = await boundaryCli.getOrgIdFromName(orgName);
|
||||
const projectId = await boundaryCli.getProjectIdFromName(
|
||||
orgId,
|
||||
projectName,
|
||||
);
|
||||
const targetId = await boundaryCli.getTargetIdFromName(
|
||||
projectId,
|
||||
targetName,
|
||||
);
|
||||
const session = await boundaryCli.authorizeSessionByTargetId(targetId);
|
||||
const retrievedUser = session.item.credentials[0].credential.username;
|
||||
const retrievedKey = session.item.credentials[0].credential.private_key;
|
||||
|
||||
expect(retrievedUser).toBe(sshUser);
|
||||
|
||||
const keyData = await readFile(sshKeyPath, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
expect(retrievedKey).toBe(keyData);
|
||||
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (orgName) {
|
||||
await boundaryCli.authenticateBoundary(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { nanoid } from 'nanoid';
|
||||
@@ -12,10 +12,9 @@ import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
import { CredentialStoresPage } from '../pages/credential-stores.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
@@ -86,9 +85,10 @@ test('Static Credential Store (User & Key Pair) @ce @aws @docker', async ({
|
||||
const keyData = await readFile(sshKeyPath, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
if (keyData != retrievedKey) {
|
||||
throw new Error('Stored Key does not match');
|
||||
}
|
||||
expect(retrievedKey).toBe(keyData);
|
||||
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (orgName) {
|
||||
await boundaryCli.authenticateBoundary(
|
||||
@@ -148,6 +148,9 @@ test('Static Credential Store (Username & Password) @ce @aws @docker', async ({
|
||||
|
||||
expect(retrievedUser).toBe(sshUser);
|
||||
expect(retrievedPassword).toBe(testPassword);
|
||||
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (orgName) {
|
||||
await boundaryCli.authenticateBoundary(
|
||||
@@ -227,6 +230,9 @@ test('Static Credential Store (JSON) @ce @aws @docker', async ({
|
||||
expect(retrievedUser).toBe(testName);
|
||||
expect(retrievedPassword).toBe(testPassword);
|
||||
expect(retrievedId).toBe(testId);
|
||||
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (orgName) {
|
||||
await boundaryCli.authenticateBoundary(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
@@ -11,6 +11,7 @@ import * as vaultCli from '../../helpers/vault-cli';
|
||||
import { CredentialStoresPage } from '../pages/credential-stores.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
const secretsPath = 'e2e_secrets';
|
||||
@@ -18,8 +19,6 @@ const secretName = 'cred';
|
||||
const secretPolicyName = 'kv-policy';
|
||||
const boundaryPolicyName = 'boundary-controller';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
await vaultCli.checkVaultCli();
|
||||
@@ -46,6 +45,7 @@ test('Vault Credential Store (User & Key Pair) @ent @aws @docker', async ({
|
||||
vaultAddr,
|
||||
}) => {
|
||||
let orgId;
|
||||
let connect;
|
||||
try {
|
||||
execSync(
|
||||
`vault policy write ${boundaryPolicyName} ./admin/tests/fixtures/boundary-controller-policy.hcl`,
|
||||
@@ -97,6 +97,7 @@ test('Vault Credential Store (User & Key Pair) @ent @aws @docker', async ({
|
||||
credentialLibraryName,
|
||||
);
|
||||
|
||||
// Verify that session can be established
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
@@ -112,8 +113,14 @@ test('Vault Credential Store (User & Key Pair) @ent @aws @docker', async ({
|
||||
projectId,
|
||||
targetName,
|
||||
);
|
||||
await boundaryCli.authorizeSessionByTargetId(targetId);
|
||||
connect = await boundaryCli.connectSshToTarget(targetId);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
import { nanoid } from 'nanoid';
|
||||
@@ -14,6 +14,7 @@ import * as vaultCli from '../../helpers/vault-cli';
|
||||
import { CredentialStoresPage } from '../pages/credential-stores.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
const secretsPath = 'e2e_secrets';
|
||||
@@ -21,8 +22,6 @@ const secretName = 'cred';
|
||||
const secretPolicyName = 'kv-policy';
|
||||
const boundaryPolicyName = 'boundary-controller';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
await vaultCli.checkVaultCli();
|
||||
@@ -114,6 +113,7 @@ test('Vault Credential Store (User & Key Pair) @ce @aws @docker', async ({
|
||||
credentialLibraryName,
|
||||
);
|
||||
|
||||
// Verify correct credentials are returned after authorizing session
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
@@ -136,13 +136,13 @@ test('Vault Credential Store (User & Key Pair) @ce @aws @docker', async ({
|
||||
session.item.credentials[0].secret.decoded.data.private_key;
|
||||
|
||||
expect(retrievedUser).toBe(sshUser);
|
||||
|
||||
const keyData = await readFile(sshKeyPath, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
if (keyData !== retrievedKey) {
|
||||
throw new Error('Stored Key does not match');
|
||||
}
|
||||
expect(retrievedKey).toBe(keyData);
|
||||
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
@@ -15,8 +15,6 @@ import { WorkersPage } from '../pages/workers.js';
|
||||
const secretPolicyName = 'kv-policy';
|
||||
const boundaryPolicyName = 'boundary-controller';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
// Setting the test timeout to 120s
|
||||
// This test can often exceed the globally defined timeout due to the number of
|
||||
// network calls.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
@@ -15,8 +15,6 @@ import { WorkersPage } from '../pages/workers.js';
|
||||
const secretPolicyName = 'kv-policy';
|
||||
const boundaryPolicyName = 'boundary-controller';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
// Setting the test timeout to 180s
|
||||
// This test can often exceed the globally defined timeout due to the number of
|
||||
// network calls.
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
import { CredentialStoresPage } from '../pages/credential-stores.js';
|
||||
import { HostCatalogsPage } from '../pages/host-catalogs.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.describe('AWS', async () => {
|
||||
test('Create an AWS Dynamic Host Catalog and set up Host Sets @ent @aws', async ({
|
||||
page,
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
adminLoginName,
|
||||
adminPassword,
|
||||
awsAccessKeyId,
|
||||
awsHostSetFilter,
|
||||
awsHostSetIps,
|
||||
awsRegion,
|
||||
awsSecretAccessKey,
|
||||
targetPort,
|
||||
sshUser,
|
||||
sshKeyPath,
|
||||
}) => {
|
||||
let orgName;
|
||||
let connect;
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
orgName = await orgsPage.createOrg();
|
||||
const projectsPage = new ProjectsPage(page);
|
||||
const projectName = await projectsPage.createProject();
|
||||
|
||||
// Create host catalog
|
||||
const hostCatalogName = 'Host Catalog ' + nanoid();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('link', { name: 'Host Catalogs' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
await page.getByLabel('Name').fill(hostCatalogName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page
|
||||
.getByRole('group', { name: 'Type' })
|
||||
.getByLabel('Dynamic')
|
||||
.click();
|
||||
await page
|
||||
.getByRole('group', { name: 'Provider' })
|
||||
.getByLabel('AWS')
|
||||
.click();
|
||||
await page.getByLabel('AWS Region').fill(awsRegion);
|
||||
await page.getByLabel('Access Key ID').fill(awsAccessKeyId);
|
||||
await page.getByLabel('Secret Access Key').fill(awsSecretAccessKey);
|
||||
await page
|
||||
.getByLabel('Disable credential rotation')
|
||||
.click({ force: true });
|
||||
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(hostCatalogName),
|
||||
).toBeVisible();
|
||||
|
||||
// Create first host set
|
||||
const hostSetName = 'Host Set ' + nanoid();
|
||||
await page.getByRole('link', { name: 'Host Sets' }).click();
|
||||
await page.getByRole('link', { name: 'New', exact: true }).click();
|
||||
await page.getByLabel('Name (Optional)').fill(hostSetName);
|
||||
await page.getByLabel('Description').fill('This is an automated test');
|
||||
await page
|
||||
.getByRole('group', { name: 'Filter' })
|
||||
.getByRole('textbox')
|
||||
.fill(awsHostSetFilter);
|
||||
await page
|
||||
.getByRole('group', { name: 'Filter' })
|
||||
.getByRole('button', { name: 'Add' })
|
||||
.click();
|
||||
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(hostSetName),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('link', { name: 'Hosts' }).click();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText('Hosts'),
|
||||
).toBeVisible();
|
||||
|
||||
// Check number of hosts in host set
|
||||
const expectedHosts = JSON.parse(awsHostSetIps);
|
||||
await expect(async () => {
|
||||
const rowCount = await page
|
||||
.getByRole('table')
|
||||
.getByRole('row')
|
||||
.filter({ hasNot: page.getByRole('columnheader') })
|
||||
.count();
|
||||
|
||||
if (rowCount !== expectedHosts.length) {
|
||||
await page.reload();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(hostSetName)
|
||||
.waitFor();
|
||||
}
|
||||
expect(rowCount).toBe(expectedHosts.length);
|
||||
}).toPass();
|
||||
|
||||
// Navigate to each host in the host set
|
||||
for (const row of await page
|
||||
.getByRole('table')
|
||||
.getByRole('rowgroup')
|
||||
.nth(1)
|
||||
.getByRole('row')
|
||||
.all()) {
|
||||
const host = row.getByRole('link');
|
||||
let hostName = await host.innerText();
|
||||
await host.click();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(hostName),
|
||||
).toBeVisible();
|
||||
|
||||
await page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(hostSetName)
|
||||
.click();
|
||||
await page.getByRole('link', { name: 'Hosts' }).click();
|
||||
}
|
||||
|
||||
// Create a target and add DHC host set as a host source
|
||||
const targetsPage = new TargetsPage(page);
|
||||
const targetName = await targetsPage.createSshTargetEnt(targetPort);
|
||||
await targetsPage.addHostSourceToTarget(hostSetName);
|
||||
|
||||
// Add another host source
|
||||
const hostCatalogsPage = new HostCatalogsPage(page);
|
||||
await hostCatalogsPage.createHostCatalog();
|
||||
const newHostSetName = await hostCatalogsPage.createHostSet();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'Resources' })
|
||||
.getByRole('link', { name: 'Targets' })
|
||||
.click();
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
await targetsPage.addHostSourceToTarget(newHostSetName);
|
||||
|
||||
// Remove the host source from the target
|
||||
await page
|
||||
.getByRole('link', { name: newHostSetName })
|
||||
.locator('..')
|
||||
.locator('..')
|
||||
.getByRole('button', { name: 'Manage' })
|
||||
.click();
|
||||
await page.getByRole('button', { name: 'Remove' }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await expect(
|
||||
page.getByRole('alert').getByText('Success', { exact: true }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
|
||||
// Set up target credentials
|
||||
const credentialStoresPage = new CredentialStoresPage(page);
|
||||
await credentialStoresPage.createStaticCredentialStore();
|
||||
const credentialName =
|
||||
await credentialStoresPage.createStaticCredentialKeyPair(
|
||||
sshUser,
|
||||
sshKeyPath,
|
||||
);
|
||||
await targetsPage.addInjectedCredentialsToTarget(
|
||||
targetName,
|
||||
credentialName,
|
||||
);
|
||||
|
||||
// Connect to target
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
adminLoginName,
|
||||
adminPassword,
|
||||
);
|
||||
const orgId = await boundaryCli.getOrgIdFromName(orgName);
|
||||
const projectId = await boundaryCli.getProjectIdFromName(
|
||||
orgId,
|
||||
projectName,
|
||||
);
|
||||
const targetId = await boundaryCli.getTargetIdFromName(
|
||||
projectId,
|
||||
targetName,
|
||||
);
|
||||
connect = await boundaryCli.connectSshToTarget(targetId);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
adminLoginName,
|
||||
adminPassword,
|
||||
);
|
||||
|
||||
if (orgName) {
|
||||
const orgId = await boundaryCli.getOrgIdFromName(orgName);
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -3,24 +3,24 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
import { CredentialStoresPage } from '../pages/credential-stores.js';
|
||||
import { HostCatalogsPage } from '../pages/host-catalogs.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.describe('AWS', async () => {
|
||||
test('Create an AWS Dynamic Host Catalog and set up Host Sets @ce @ent @aws', async ({
|
||||
test('Create an AWS Dynamic Host Catalog and set up Host Sets @ce @aws', async ({
|
||||
page,
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
@@ -32,13 +32,16 @@ test.describe('AWS', async () => {
|
||||
awsRegion,
|
||||
awsSecretAccessKey,
|
||||
targetPort,
|
||||
sshUser,
|
||||
sshKeyPath,
|
||||
}) => {
|
||||
let orgName;
|
||||
let connect;
|
||||
try {
|
||||
const orgsPage = new OrgsPage(page);
|
||||
orgName = await orgsPage.createOrg();
|
||||
const projectsPage = new ProjectsPage(page);
|
||||
await projectsPage.createProject();
|
||||
const projectName = await projectsPage.createProject();
|
||||
|
||||
// Create host catalog
|
||||
const hostCatalogName = 'Host Catalog ' + nanoid();
|
||||
@@ -107,37 +110,32 @@ test.describe('AWS', async () => {
|
||||
).toBeVisible();
|
||||
|
||||
// Check number of hosts in host set
|
||||
let i = 0;
|
||||
let rowCount = 0;
|
||||
let expectedHosts = JSON.parse(awsHostSetIps);
|
||||
do {
|
||||
i = i + 1;
|
||||
// Getting the number of rows in the second rowgroup (the first rowgroup is the header row)
|
||||
rowCount = await page
|
||||
const expectedHosts = JSON.parse(awsHostSetIps);
|
||||
await expect(async () => {
|
||||
const rowCount = await page
|
||||
.getByRole('table')
|
||||
.getByRole('rowgroup')
|
||||
.nth(1)
|
||||
.getByRole('row')
|
||||
.filter({ hasNot: page.getByRole('columnheader') })
|
||||
.count();
|
||||
await page.reload();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(hostSetName)
|
||||
.waitFor();
|
||||
} while (i < 5);
|
||||
|
||||
expect(rowCount).toBe(expectedHosts.length);
|
||||
if (rowCount !== expectedHosts.length) {
|
||||
await page.reload();
|
||||
await page
|
||||
.getByRole('navigation', { name: 'breadcrumbs' })
|
||||
.getByText(hostSetName)
|
||||
.waitFor();
|
||||
}
|
||||
expect(rowCount).toBe(expectedHosts.length);
|
||||
}).toPass();
|
||||
|
||||
// Navigate to each host in the host set
|
||||
for (let i = 0; i < expectedHosts.length; i++) {
|
||||
const host = await page
|
||||
.getByRole('table')
|
||||
.getByRole('rowgroup')
|
||||
.nth(1)
|
||||
.getByRole('row')
|
||||
.nth(i)
|
||||
.getByRole('link');
|
||||
|
||||
for (const row of await page
|
||||
.getByRole('table')
|
||||
.getByRole('rowgroup')
|
||||
.nth(1)
|
||||
.getByRole('row')
|
||||
.all()) {
|
||||
const host = row.getByRole('link');
|
||||
let hostName = await host.innerText();
|
||||
await host.click();
|
||||
await expect(
|
||||
@@ -183,9 +181,43 @@ test.describe('AWS', async () => {
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
|
||||
// Add the host source back
|
||||
await targetsPage.addHostSourceToTarget(newHostSetName);
|
||||
// Set up target credentials
|
||||
const credentialStoresPage = new CredentialStoresPage(page);
|
||||
await credentialStoresPage.createStaticCredentialStore();
|
||||
const credentialName =
|
||||
await credentialStoresPage.createStaticCredentialKeyPair(
|
||||
sshUser,
|
||||
sshKeyPath,
|
||||
);
|
||||
await targetsPage.addBrokeredCredentialsToTarget(
|
||||
targetName,
|
||||
credentialName,
|
||||
);
|
||||
|
||||
// Connect to target
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
adminLoginName,
|
||||
adminPassword,
|
||||
);
|
||||
const orgId = await boundaryCli.getOrgIdFromName(orgName);
|
||||
const projectId = await boundaryCli.getProjectIdFromName(
|
||||
orgId,
|
||||
projectName,
|
||||
);
|
||||
const targetId = await boundaryCli.getTargetIdFromName(
|
||||
projectId,
|
||||
targetName,
|
||||
);
|
||||
connect = await boundaryCli.connectSshToTarget(targetId);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
} finally {
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
|
||||
await boundaryCli.authenticateBoundary(
|
||||
baseURL,
|
||||
adminAuthMethodId,
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { StoragePoliciesPage } from '../pages/storage-policies.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test('Global Settings @ent @aws @docker', async ({
|
||||
page,
|
||||
baseURL,
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
import { GroupsPage } from '../pages/groups.js';
|
||||
import { OrgsPage } from '../pages/orgs.js';
|
||||
import { RolesPage } from '../pages/roles.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
|
||||
@@ -8,6 +8,9 @@ import { expect } from '@playwright/test';
|
||||
|
||||
import { LoginPage } from '../pages/login.js';
|
||||
|
||||
// Reset storage state for this file to avoid being authenticated
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
|
||||
test('Log in, log out, and then log back in @ce @ent @aws @docker', async ({
|
||||
page,
|
||||
adminLoginName,
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as boundaryHttp from '../../helpers/boundary-http.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test('Search and Pagination (Targets) @ce @ent @aws @docker', async ({
|
||||
page,
|
||||
request,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
@@ -17,8 +17,6 @@ import { StorageBucketsPage } from '../pages/storage-buckets.js';
|
||||
import { StoragePoliciesPage } from '../pages/storage-policies.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
@@ -227,6 +225,11 @@ test('Session Recording Test (AWS) @ent @aws', async ({
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
await targetsPage.detachStorageBucket();
|
||||
} finally {
|
||||
// End `boundary connect` process
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
|
||||
if (policyName) {
|
||||
const storagePolicyId = await boundaryCli.getPolicyIdFromName(
|
||||
orgId,
|
||||
@@ -240,9 +243,5 @@ test('Session Recording Test (AWS) @ent @aws', async ({
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
// End `boundary connect` process
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
@@ -17,8 +17,6 @@ import { StorageBucketsPage } from '../pages/storage-buckets.js';
|
||||
import { StoragePoliciesPage } from '../pages/storage-policies.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
@@ -234,6 +232,11 @@ test('Session Recording Test (MinIO) @ent @docker', async ({
|
||||
await page.getByRole('link', { name: targetName }).click();
|
||||
await targetsPage.detachStorageBucket();
|
||||
} finally {
|
||||
// End `boundary connect` process
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
|
||||
if (policyName) {
|
||||
const storagePolicyId = await boundaryCli.getPolicyIdFromName(
|
||||
orgId,
|
||||
@@ -247,9 +250,5 @@ test('Session Recording Test (MinIO) @ent @docker', async ({
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
// End `boundary connect` process
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
@@ -21,8 +21,6 @@ const secretsPath = 'e2e_secrets';
|
||||
// This must match the secret name in ssh-policy.hcl
|
||||
const secretName = 'boundary-client';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
await vaultCli.checkVaultCli();
|
||||
@@ -61,8 +59,8 @@ test('SSH Certificate Injection @ent @docker', async ({
|
||||
`vault write ${secretsPath}/roles/${secretName} @./admin/tests/fixtures/ssh-certificate-injection-role.json`,
|
||||
);
|
||||
|
||||
const private_key = atob(sshCaKey);
|
||||
const public_key = atob(sshCaKeyPublic);
|
||||
const private_key = Buffer.from(sshCaKey, 'base64');
|
||||
const public_key = Buffer.from(sshCaKeyPublic, 'base64');
|
||||
|
||||
execSync(
|
||||
`vault write ${secretsPath}/config/ca` +
|
||||
@@ -136,20 +134,16 @@ test('SSH Certificate Injection @ent @docker', async ({
|
||||
connect = await boundaryCli.connectSshToTarget(targetId);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
await page
|
||||
.getByRole('cell', { name: targetName })
|
||||
.locator('..')
|
||||
.getByRole('button', { name: 'Cancel' })
|
||||
.click();
|
||||
} finally {
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
// End `boundary connect` process
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
|
||||
execSync(`vault secrets disable ${secretsPath}`);
|
||||
execSync(`vault policy delete ${sshPolicyName}`);
|
||||
execSync(`vault policy delete ${boundaryPolicyName}`);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
@@ -19,8 +19,6 @@ const secretName = 'cred';
|
||||
const secretPolicyName = 'kv-policy';
|
||||
const boundaryPolicyName = 'boundary-controller';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
await vaultCli.checkVaultCli();
|
||||
@@ -126,18 +124,14 @@ test('SSH Credential Injection (Vault User & Key Pair) @ent @docker', async ({
|
||||
connect = await boundaryCli.connectSshToTarget(targetId);
|
||||
const sessionsPage = new SessionsPage(page);
|
||||
await sessionsPage.waitForSessionToBeVisible(targetName);
|
||||
await page
|
||||
.getByRole('cell', { name: targetName })
|
||||
.locator('..')
|
||||
.getByRole('button', { name: 'Cancel' })
|
||||
.click();
|
||||
} finally {
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
// End `boundary connect` process
|
||||
if (connect) {
|
||||
connect.kill('SIGTERM');
|
||||
}
|
||||
|
||||
if (orgId) {
|
||||
await boundaryCli.deleteScope(orgId);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
@@ -14,8 +14,6 @@ import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as boundaryCli from '../../helpers/boundary-cli';
|
||||
@@ -14,8 +14,6 @@ import { ProjectsPage } from '../pages/projects.js';
|
||||
import { SessionsPage } from '../pages/sessions.js';
|
||||
import { TargetsPage } from '../pages/targets.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await boundaryCli.checkBoundaryCli();
|
||||
});
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test('Create a worker (enterprise) @ent @docker @aws', async ({
|
||||
page,
|
||||
browserName,
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
import { WorkersPage } from '../pages/workers.js';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test('Worker Tags @ce @ent @aws @docker', async ({ page }) => {
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10);
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, authenticatedState } from '../../global-setup.js';
|
||||
import { test } from '../../global-setup.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
test.use({ storageState: authenticatedState });
|
||||
|
||||
test('Create a worker @ce @docker @aws', async ({ page, browserName }) => {
|
||||
test.skip(browserName === 'webkit', 'Bug in worker form on Safari');
|
||||
|
||||
|
||||
@@ -3,7 +3,50 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { exec } from 'node:child_process';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
const remoteCommand =
|
||||
'for i in {1..15}; do echo helloworld\\$i; sleep 1s; done';
|
||||
|
||||
/**
|
||||
* Spawns a child process for a Boundary connection and returns a promise.
|
||||
* Resolves on any output from stdout or stderr.
|
||||
* @param {string} command
|
||||
* @param {Array<string>} args
|
||||
*/
|
||||
async function spawnConnection(command, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const childProcess = spawn(command, args, {
|
||||
shell: true,
|
||||
});
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
if (data.toString().includes('helloworld3')) {
|
||||
resolve({ childProcess, stdout: data.toString() });
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
if (!data.toString().includes('Warning: Permanently added')) {
|
||||
reject(data.toString());
|
||||
}
|
||||
});
|
||||
childProcess.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
// In case the process has no stdio and didn't error out, resolve a
|
||||
// promise once the child process closes so we're not waiting forever.
|
||||
// Windows doesn't seem to return any error nor any output from stderr when
|
||||
// a timeout occurs so this guarantees we return a response to the caller.
|
||||
// Otherwise this should not get hit as we should be returning a response
|
||||
// from one of the handlers above.
|
||||
childProcess.on('close', () => {
|
||||
resolve({
|
||||
childProcess,
|
||||
stderr: JSON.stringify({ error: 'Process was closed.' }),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the specified target
|
||||
@@ -14,23 +57,61 @@ import { exec } from 'node:child_process';
|
||||
* @returns ChildProcess representing the result of the command execution
|
||||
*/
|
||||
export async function connectToTarget(targetId, sshUser, sshKeyPath) {
|
||||
let connect;
|
||||
try {
|
||||
connect = exec(
|
||||
`boundary connect -target-id=${targetId}` +
|
||||
' -exec /usr/bin/ssh --' +
|
||||
` -l ${sshUser}` +
|
||||
` -i ${sshKeyPath}` +
|
||||
' -o UserKnownHostsFile=/dev/null' +
|
||||
' -o StrictHostKeyChecking=no' +
|
||||
' -o IdentitiesOnly=yes' + // forces the use of the provided key
|
||||
' -p {{boundary.port}}' +
|
||||
' {{boundary.ip}}',
|
||||
);
|
||||
const { childProcess } = await spawnConnection('boundary', [
|
||||
'connect',
|
||||
`-target-id=${targetId}`,
|
||||
'-exec',
|
||||
'/usr/bin/ssh',
|
||||
'--',
|
||||
'-T', // forces tty allocation
|
||||
`-l ${sshUser}`,
|
||||
`-i ${sshKeyPath}`,
|
||||
'-o UserKnownHostsFile=/dev/null',
|
||||
'-o StrictHostKeyChecking=no',
|
||||
'-o IdentitiesOnly=yes', // forces the use of the provided key
|
||||
'-p {{boundary.port}}',
|
||||
'{{boundary.ip}}',
|
||||
`"${remoteCommand}"`,
|
||||
]);
|
||||
|
||||
return childProcess;
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
throw new Error(`Failed to connect to the target: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the specified alias
|
||||
* exec is used here to keep the session open
|
||||
* @param {string} alias alias of the target to be connected to
|
||||
* @param {string} sshUser User to be used for the ssh connection
|
||||
* @param {string} sshKeyPath Path to the ssh key to be used for the ssh connection
|
||||
* @returns ChildProcess representing the result of the command execution
|
||||
*/
|
||||
export async function connectToAlias(alias, sshUser, sshKeyPath) {
|
||||
try {
|
||||
const { childProcess } = await spawnConnection('boundary', [
|
||||
'connect',
|
||||
`${alias}`,
|
||||
'-exec',
|
||||
'/usr/bin/ssh',
|
||||
'--',
|
||||
'-T', // forces tty allocation
|
||||
`-l ${sshUser}`,
|
||||
`-i ${sshKeyPath}`,
|
||||
'-o UserKnownHostsFile=/dev/null',
|
||||
'-o StrictHostKeyChecking=no',
|
||||
'-o IdentitiesOnly=yes', // forces the use of the provided key
|
||||
'-p {{boundary.port}}',
|
||||
'{{boundary.ip}}',
|
||||
`"${remoteCommand}"`,
|
||||
]);
|
||||
|
||||
return childProcess;
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to connect to the target: ${e}`);
|
||||
}
|
||||
return connect;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,18 +121,47 @@ export async function connectToTarget(targetId, sshUser, sshKeyPath) {
|
||||
* @returns ChildProcess representing the result of the command execution
|
||||
*/
|
||||
export async function connectSshToTarget(targetId) {
|
||||
let connect;
|
||||
try {
|
||||
connect = exec(
|
||||
'boundary connect ssh' +
|
||||
` -target-id=${targetId}` +
|
||||
' --' +
|
||||
' -o UserKnownHostsFile=/dev/null' +
|
||||
' -o StrictHostKeyChecking=no' +
|
||||
' -o IdentitiesOnly=yes', // forces the use of the provided key
|
||||
);
|
||||
const { childProcess } = await spawnConnection('boundary', [
|
||||
'connect',
|
||||
'ssh',
|
||||
`-target-id=${targetId}`,
|
||||
`-remote-command="${remoteCommand}"`,
|
||||
'--',
|
||||
'-T', // forces tty allocation
|
||||
'-o UserKnownHostsFile=/dev/null',
|
||||
'-o StrictHostKeyChecking=no',
|
||||
'-o IdentitiesOnly=yes', // forces the use of the provided key
|
||||
]);
|
||||
|
||||
return childProcess;
|
||||
} catch (e) {
|
||||
console.log(`${e.stderr}`);
|
||||
throw new Error(`Failed to connect to the target: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects via ssh to the specified alias
|
||||
* exec is used here to keep the session open
|
||||
* @param {string} alias alias of the target to be connected to
|
||||
* @returns ChildProcess representing the result of the command execution
|
||||
*/
|
||||
export async function connectSshToAlias(alias) {
|
||||
try {
|
||||
const { childProcess } = await spawnConnection('boundary', [
|
||||
'connect',
|
||||
`ssh`,
|
||||
`${alias}`,
|
||||
`-remote-command="${remoteCommand}"`,
|
||||
'--',
|
||||
'-T', // forces tty allocation
|
||||
'-o UserKnownHostsFile=/dev/null',
|
||||
'-o StrictHostKeyChecking=no',
|
||||
'-o IdentitiesOnly=yes', // forces the use of the provided key
|
||||
]);
|
||||
|
||||
return childProcess;
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to connect to the target: ${e}`);
|
||||
}
|
||||
return connect;
|
||||
}
|
||||
Reference in new issue
Block a user