test: 💍 add e2e tests for credentials display in desktop (#2660)
* test: 💍 add e2e tests for credentials display in desktop
This commit is contained in:
1 parent
deff38de56
commit
064375d457
5 files changed
+398
No files matched your search
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,3 +10,4 @@ export * from './boundary-http/host-sets.js';
|
|||||||
export * from './boundary-http/hosts.js';
|
export * from './boundary-http/hosts.js';
|
||||||
export * from './boundary-http/credential-stores.js';
|
export * from './boundary-http/credential-stores.js';
|
||||||
export * from './boundary-http/credentials.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);
|
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);
|
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);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user