test: 💍 add e2e tests for credentials display in desktop (#2660)

* test: 💍 add e2e tests for credentials display in desktop
This commit is contained in:
Dharini Jeeva authored and GitHub committed 2025-01-23 14:30:54 -08:00
1 parent deff38de56
commit 064375d457
5 files changed
+398

No files matched your search

+311
View File
@@ -0,0 +1,311 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import path from 'path';
import { expect, test } from '../fixtures/baseTest.js';
import * as boundaryHttp from '../../helpers/boundary-http.js';
import { spawnSync, execSync } from 'child_process';
import * as vaultCli from '../../helpers/vault-cli';
let org;
let targetWithBrokeredVaultCredentials;
let targetWithBrokeredStaticCredentials;
let sshPrivateKeyCredential;
let jsonCredential;
let vaultGenericCredentialLibrary;
// config for vault setup
const secretPolicyName = 'kv-policy';
const boundaryPolicyName = 'boundary-controller';
const secretsPath = 'e2e_secrets';
const secretName = 'cred';
const __dirname = import.meta.dirname;
const boundaryControllerPath = path.resolve(
__dirname,
'../../admin/tests/fixtures/boundary-controller-policy.hcl',
);
const kvPolicyPath = path.resolve(
__dirname,
'../../admin/tests/fixtures/kv-policy.hcl',
);
test.beforeAll(async () => {
await vaultCli.checkVaultCli();
});
test.beforeEach(
async ({
request,
targetAddress,
targetPort,
sshUser,
sshKeyPath,
vaultAddr,
}) => {
execSync(`vault policy delete ${secretPolicyName}`);
execSync(`vault policy delete ${boundaryPolicyName}`);
execSync(`vault secrets disable ${secretsPath}`);
org = await boundaryHttp.createOrg(request);
const project = await boundaryHttp.createProject(request, org.id);
// Create host set
const hostCatalog = await boundaryHttp.createStaticHostCatalog(
request,
project.id,
);
const host = await boundaryHttp.createHost(request, {
hostCatalogId: hostCatalog.id,
address: targetAddress,
});
const hostSet = await boundaryHttp.createHostSet(request, hostCatalog.id);
await boundaryHttp.addHostToHostSet(request, {
hostSet,
hostIds: [host.id],
});
// Create a static credential store
const credentialStore = await boundaryHttp.createStaticCredentialStore(
request,
project.id,
);
// Create a key pair credential
sshPrivateKeyCredential = await boundaryHttp.createStaticCredentialKeyPair(
request,
{
credentialStoreId: credentialStore.id,
username: sshUser,
sshKeyPath,
},
);
// Create a json type credential
jsonCredential = await boundaryHttp.createStaticCredentialJson(request, {
credentialStoreId: credentialStore.id,
data: {
nested: {
key1: String.raw`val\bue1`,
key2: String.raw`val\tue2`,
},
},
});
// Create Vault token and apply policies
spawnSync('vault', [
'policy',
'write',
boundaryPolicyName,
boundaryControllerPath,
]);
execSync(`vault secrets enable -path=${secretsPath} kv-v2`);
spawnSync(
'vault',
[
'kv',
'put',
'-mount',
secretsPath,
secretName,
`password=${String.raw`pass\word`}`,
],
{
encoding: 'utf-8',
},
);
spawnSync('vault', ['policy', 'write', secretPolicyName, kvPolicyPath]);
const vaultToken = JSON.parse(
execSync(
`vault token create` +
` -no-default-policy=true` +
` -policy=${boundaryPolicyName}` +
` -policy=${secretPolicyName}` +
` -orphan=true` +
` -period=20m` +
` -renewable=true` +
` -format=json`,
),
);
const clientToken = vaultToken.auth.client_token;
// Create a vault credential store and generic secrets type credential library
const vaultCredentialStore = await boundaryHttp.createVaultCredentialStore(
request,
project.id,
vaultAddr,
clientToken,
);
vaultGenericCredentialLibrary =
await boundaryHttp.createVaultGenericSecretsCredentialLibrary(request, {
credentialStoreId: vaultCredentialStore.id,
vaultPath: `${secretsPath}/data/${secretName}`,
});
// Create a tcp target with vault brokered credential Libraries
targetWithBrokeredVaultCredentials = await boundaryHttp.createTarget(
request,
{
scopeId: project.id,
type: 'tcp',
port: targetPort,
},
);
targetWithBrokeredVaultCredentials = await boundaryHttp.addHostSource(
request,
{
target: targetWithBrokeredVaultCredentials,
hostSourceIds: [hostSet.id],
},
);
targetWithBrokeredVaultCredentials =
await boundaryHttp.addBrokeredCredentials(request, {
target: targetWithBrokeredVaultCredentials,
credentialIds: [vaultGenericCredentialLibrary.id],
});
// Create a tcp target with static brokered credentials
targetWithBrokeredStaticCredentials = await boundaryHttp.createTarget(
request,
{
scopeId: project.id,
type: 'tcp',
port: targetPort,
},
);
targetWithBrokeredStaticCredentials = await boundaryHttp.addHostSource(
request,
{
target: targetWithBrokeredStaticCredentials,
hostSourceIds: [hostSet.id],
},
);
targetWithBrokeredStaticCredentials =
await boundaryHttp.addBrokeredCredentials(request, {
target: targetWithBrokeredStaticCredentials,
credentialIds: [sshPrivateKeyCredential.id, jsonCredential.id],
});
},
);
test.afterEach(async ({ request }) => {
if (org) {
await boundaryHttp.deleteOrg(request, org.id);
}
});
test.describe('Credential Panel tests', async () => {
test('Display Vault brokered Credentials and handle special characters', async ({
authedPage,
}) => {
await authedPage
.getByRole('link', { name: targetWithBrokeredVaultCredentials.name })
.click();
await authedPage.getByRole('button', { name: 'Connect' }).click();
await expect(
authedPage.getByRole('heading', { name: 'Sessions' }),
).toBeVisible();
await expect(
authedPage.getByText(vaultGenericCredentialLibrary.name),
).toBeVisible();
await expect(authedPage.getByText('password')).toBeVisible();
await authedPage
.getByRole('listitem')
.filter({ hasText: 'password' })
.getByLabel('Toggle secret visibility')
.click();
await expect(
authedPage
.getByRole('listitem')
.filter({ hasText: 'password' })
.locator('pre'),
).toHaveText(String.raw`pass\word`);
// End session as active sessions will show a popup when trying to close the DC
await authedPage.getByRole('button', { name: 'End Session' }).click();
await expect(authedPage.getByText('Canceled successfully.')).toBeVisible();
await expect(
authedPage.getByRole('heading', { name: 'Targets' }),
).toBeVisible();
});
test('Display JSON static credential in a key value format and handle special characters', async ({
authedPage,
}) => {
await authedPage
.getByRole('link', { name: targetWithBrokeredStaticCredentials.name })
.click();
await authedPage.getByRole('button', { name: 'Connect' }).click();
await expect(
authedPage.getByRole('heading', { name: 'Sessions' }),
).toBeVisible();
// JSON credential with nested keys
await expect(authedPage.getByText(jsonCredential.name)).toBeVisible();
await expect(authedPage.getByText('nested.key1')).toBeVisible();
await expect(authedPage.getByText('nested.key2')).toBeVisible();
await authedPage
.getByRole('listitem')
.filter({ hasText: 'nested.key1' })
.getByLabel('Toggle secret visibility')
.click();
await expect(
authedPage
.getByRole('listitem')
.filter({ hasText: 'nested.key1' })
.locator('pre'),
).toHaveText(String.raw`val\bue1`);
await authedPage
.getByRole('listitem')
.filter({ hasText: 'nested.key2' })
.getByLabel('Toggle secret visibility')
.click();
await expect(
authedPage
.getByRole('listitem')
.filter({ hasText: 'nested.key2' })
.locator('pre'),
).toHaveText(String.raw`val\tue2`);
// SSH private key credential
await expect(
authedPage.getByText(sshPrivateKeyCredential.name),
).toBeVisible();
await expect(authedPage.getByText('private_key')).toBeVisible();
await expect(authedPage.getByText('username')).toBeVisible();
await authedPage
.getByRole('listitem')
.filter({ hasText: 'username' })
.getByLabel('Toggle secret visibility')
.click();
await expect(
authedPage
.getByRole('listitem')
.filter({ hasText: 'username' })
.locator('pre'),
).toHaveText(sshPrivateKeyCredential.attributes.username);
await authedPage
.getByRole('listitem')
.filter({ hasText: 'private_key' })
.getByLabel('Toggle secret visibility')
.click();
await expect(
authedPage
.getByRole('listitem')
.filter({ hasText: 'private_key' })
.locator('pre'),
).toContainText(/BEGIN (OPENSSH|RSA) PRIVATE KEY/);
// End session as active sessions will show a popup when trying to close the DC
await authedPage.getByRole('button', { name: 'End Session' }).click();
await expect(authedPage.getByText('Canceled successfully.')).toBeVisible();
await expect(
authedPage.getByRole('heading', { name: 'Targets' }),
).toBeVisible();
});
});
+1
View File
@@ -10,3 +10,4 @@ export * from './boundary-http/host-sets.js';
export * from './boundary-http/hosts.js';
export * from './boundary-http/credential-stores.js';
export * from './boundary-http/credentials.js';
export * from './boundary-http/credential-libraries.js';
@@ -0,0 +1,32 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { nanoid } from 'nanoid';
import { checkResponse } from './responseHelper.js';
/**
* Creates a vault generic credential library
* @param {import('@playwright/test').APIRequestContext} request
* @param {string} credentialStoreId ID of the credential store where the credential will be created
* @param {string} vaultPath Path to secret in vault
* @returns {Promise<Serializable>}
*/
export async function createVaultGenericSecretsCredentialLibrary(
request,
{ credentialStoreId, vaultPath },
) {
const response = await request.post(`/v1/credential-libraries`, {
data: {
name: `vault-credential-store-${nanoid()}`,
credential_store_id: credentialStoreId,
type: 'vault-generic',
attributes: {
path: vaultPath,
},
},
});
return checkResponse(response);
}
@@ -23,3 +23,32 @@ export async function createStaticCredentialStore(request, projectId) {
return checkResponse(response);
}
/**
* Creates a new vault credential store
* @param {import('@playwright/test').APIRequestContext} request
* @param {string} projectId ID of the project where the credential store will be created
* @param {string} vaultAddr Address of the vault server
* @param {string} vaultToken Token for the vault server
* @returns {Promise<Serializable>}
*/
export async function createVaultCredentialStore(
request,
projectId,
vaultAddr,
vaultToken,
) {
const response = await request.post(`/v1/credential-stores`, {
data: {
name: `vault-credential-store-${nanoid()}`,
scope_id: projectId,
type: 'vault',
attributes: {
address: vaultAddr,
token: vaultToken,
},
},
});
return checkResponse(response);
}
@@ -37,3 +37,28 @@ export async function createStaticCredentialKeyPair(
return checkResponse(response);
}
/**
* Creates a new json credential
* @param {import('@playwright/test').APIRequestContext} request
* @param {string} credentialStoreId ID of the credential store where the credential will be created
* @param {object} data JSON credential data
* @returns {Promise<Serializable>}
*/
export async function createStaticCredentialJson(
request,
{ credentialStoreId, data },
) {
const response = await request.post(`/v1/credentials`, {
data: {
name: `static-credential-store-${nanoid()}`,
credential_store_id: credentialStoreId,
type: 'json',
attributes: {
object: data,
},
},
});
return checkResponse(response);
}