mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
* VAULT-42427 - initial code updates for aws form * VAULT-42756 - implemented wif support for secret sync * VAULT-42756 - added acceptance and integration test cases for WIF support * refactor: streamline WIF credential handling and enhance destination details management * added changelog * fixed review comments * updated changelog * fixed failing tests * VAULT-42756 - add Playwright tests for sync destination WIF workflows * fixed copilot review comments * fixed review comments * fixed validation for Edit scenario * fixed region field to have no default value selected * Refactor: updated string literals with centralized enums and some other refactors * fixed review comments to remove redundant click actions * refactor sync destination form handling to separate function in tests * add mock responses for sync destinations in tests * added copyright header to mock file Co-authored-by: mohit-hashicorp <mohit.ojha@hashicorp.com>
This commit is contained in:
parent
d89abd6623
commit
57d27929a1
@ -63,7 +63,9 @@ export class BasePage {
|
||||
if (options?.external) {
|
||||
// Prerequisite: mock plugin catalog endpoint in the test so the External plugin option is available.
|
||||
await this.page.locator('label:nth-child(2) > .hds-form-radio-card__control-wrapper').click();
|
||||
await this.page.getByLabel('Plugin version Required').selectOption(options.pluginVersion);
|
||||
if (options.pluginVersion) {
|
||||
await this.page.getByLabel('Plugin version Required').selectOption(options.pluginVersion);
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.defaultLeaseTtl) {
|
||||
|
||||
190
ui/e2e/tests/superuser/sync-destinations.spec.ts
Normal file
190
ui/e2e/tests/superuser/sync-destinations.spec.ts
Normal file
@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2016, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import { BasePage } from '../../pages/base';
|
||||
import {
|
||||
SYNC_DESTINATION_AWS_WIF_RESPONSE,
|
||||
SYNC_DESTINATION_AZURE_WIF_RESPONSE,
|
||||
SYNC_DESTINATION_GCP_WIF_RESPONSE,
|
||||
} from '../../../tests/helpers/sync/mocks';
|
||||
|
||||
/**
|
||||
* Navigate to the sync destination creation form
|
||||
* @param page - The Playwright page object
|
||||
* @param type - The destination type ('aws-sm', 'azure-kv', 'gcp-sm')
|
||||
*/
|
||||
async function openCreateDestinationForm(page: Page, type: string) {
|
||||
await page.goto('dashboard');
|
||||
await page.getByRole('link', { name: 'Secrets', exact: true }).click();
|
||||
await page.getByRole('link', { name: 'Secrets sync' }).click();
|
||||
|
||||
const enableButton = page.getByRole('button', { name: 'Enable' });
|
||||
// waitFor auto-waits for the page to render after navigation; catch means secrets sync is already activated
|
||||
const needsActivation = await enableButton
|
||||
.waitFor({ state: 'visible', timeout: 100 })
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (needsActivation) {
|
||||
await enableButton.click();
|
||||
await page.getByRole('checkbox', { name: "I've read the above linked" }).check();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
}
|
||||
|
||||
await page.getByRole('link', { name: 'Create first destination' }).click();
|
||||
await page.locator(`[data-test-select-destination="${type}"]`).click();
|
||||
}
|
||||
|
||||
test('sync destination wif workflow for aws', async ({ page }) => {
|
||||
const basePage = new BasePage(page);
|
||||
|
||||
// Set up route mocks before any navigation
|
||||
await page.route('**/v1/sys/sync/destinations/aws-sm/test-aws', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(SYNC_DESTINATION_AWS_WIF_RESPONSE),
|
||||
});
|
||||
});
|
||||
|
||||
await test.step('navigate to open new create destination form for AWS Secrets Manager', async () => {
|
||||
await openCreateDestinationForm(page, 'aws-sm');
|
||||
});
|
||||
await expect(page.getByRole('radio', { name: 'Workload Identity Federation' })).toBeVisible();
|
||||
await page.getByRole('radio', { name: 'Workload Identity Federation' }).check();
|
||||
await page.getByRole('button', { name: 'Create destination' }).click();
|
||||
|
||||
await expect(page.getByText('Name is required.')).toBeVisible();
|
||||
await expect(page.getByText('Role ARN is required.')).toBeVisible();
|
||||
await expect(page.getByText('Identity token audience is required.')).toBeVisible();
|
||||
|
||||
await page.getByRole('textbox', { name: 'Name' }).fill('test-aws');
|
||||
await page.getByRole('textbox', { name: 'Role ARN' }).fill('arn:aws:iam::111111111111:role/wif_test');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'identity_token_audience' })
|
||||
.fill('vault-test.wif-test.sbx.hashidemos.io/v1/identity/oidc/secrets-sync');
|
||||
|
||||
// Set up response watchers before clicking to avoid missing fast responses
|
||||
const postResponse = page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/v1/sys/sync/destinations/aws-sm/test-aws') && resp.request().method() === 'POST'
|
||||
);
|
||||
const getResponse = page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/v1/sys/sync/destinations/aws-sm/test-aws') && resp.request().method() === 'GET'
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Create destination' }).click();
|
||||
await Promise.all([postResponse, getResponse]);
|
||||
await expect(page.getByText('Connection successful', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('You have successfully created a sync destination')).toBeVisible();
|
||||
await basePage.dismissFlashMessages();
|
||||
|
||||
// Verify that the details page shows the correct information from the mocked response
|
||||
await expect(page.getByRole('heading', { name: 'test-aws' })).toBeVisible();
|
||||
await expect(page.getByText('WIF', { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('sync destination wif workflow for azure', async ({ page }) => {
|
||||
const basePage = new BasePage(page);
|
||||
|
||||
await page.route('**/v1/sys/sync/destinations/azure-kv/test-azure', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(SYNC_DESTINATION_AZURE_WIF_RESPONSE),
|
||||
});
|
||||
});
|
||||
|
||||
await test.step('navigate to open new create destination form for Azure Key Vault', async () => {
|
||||
await openCreateDestinationForm(page, 'azure-kv');
|
||||
});
|
||||
await expect(page.getByRole('radio', { name: 'Workload Identity Federation' })).toBeVisible();
|
||||
await page.getByRole('radio', { name: 'Workload Identity Federation' }).check();
|
||||
await page.getByRole('button', { name: 'Create destination' }).click();
|
||||
|
||||
await expect(page.getByText('Name is required.')).toBeVisible();
|
||||
await expect(page.getByText('Key Vault URI is required.')).toBeVisible();
|
||||
await expect(page.getByText('Tenant ID is required.')).toBeVisible();
|
||||
await expect(page.getByText('Client ID is required.')).toBeVisible();
|
||||
await expect(page.getByText('Identity token audience is required.')).toBeVisible();
|
||||
|
||||
await page.getByRole('textbox', { name: 'Name' }).fill('test-azure');
|
||||
await page.getByRole('textbox', { name: 'Key Vault URI' }).fill('https://test-keyvault.vault.azure.net');
|
||||
await page.getByRole('textbox', { name: 'Tenant ID' }).fill('11111111-1111-1111-1111-111111111111');
|
||||
await page.getByRole('textbox', { name: 'Client ID' }).fill('test-client-id');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'identity_token_audience' })
|
||||
.fill('https://test-audience.azure.com');
|
||||
|
||||
const postResponse = page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/v1/sys/sync/destinations/azure-kv/test-azure') &&
|
||||
resp.request().method() === 'POST'
|
||||
);
|
||||
const getResponse = page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/v1/sys/sync/destinations/azure-kv/test-azure') &&
|
||||
resp.request().method() === 'GET'
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Create destination' }).click();
|
||||
await Promise.all([postResponse, getResponse]);
|
||||
await expect(page.getByText('Connection successful', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('You have successfully created a sync destination')).toBeVisible();
|
||||
await basePage.dismissFlashMessages();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'test-azure' })).toBeVisible();
|
||||
await expect(page.getByText('WIF', { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('sync destination wif workflow for gcp', async ({ page }) => {
|
||||
const basePage = new BasePage(page);
|
||||
|
||||
await page.route('**/v1/sys/sync/destinations/gcp-sm/test-gcp', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(SYNC_DESTINATION_GCP_WIF_RESPONSE),
|
||||
});
|
||||
});
|
||||
|
||||
await test.step('navigate to open new create destination form for Google Secret Manager', async () => {
|
||||
await openCreateDestinationForm(page, 'gcp-sm');
|
||||
});
|
||||
await expect(page.getByRole('radio', { name: 'Workload Identity Federation' })).toBeVisible();
|
||||
await page.getByRole('radio', { name: 'Workload Identity Federation' }).check();
|
||||
await page.getByRole('button', { name: 'Create destination' }).click();
|
||||
|
||||
await expect(page.getByText('Name is required.')).toBeVisible();
|
||||
await expect(page.getByText('Project ID is required.')).toBeVisible();
|
||||
await expect(page.getByText('Service account email is required.')).toBeVisible();
|
||||
await expect(page.getByText('Identity token audience is required.')).toBeVisible();
|
||||
|
||||
await page.getByRole('textbox', { name: 'Name' }).fill('test-gcp');
|
||||
await page.getByRole('textbox', { name: 'Project ID' }).fill('test-gcp-project');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Service account email' })
|
||||
.fill('test-sa@test-gcp-project.iam.gserviceaccount.com');
|
||||
await page.getByRole('textbox', { name: 'identity_token_audience' }).fill('https://test-audience.gcp.com');
|
||||
|
||||
const postResponse = page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/v1/sys/sync/destinations/gcp-sm/test-gcp') && resp.request().method() === 'POST'
|
||||
);
|
||||
const getResponse = page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/v1/sys/sync/destinations/gcp-sm/test-gcp') && resp.request().method() === 'GET'
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Create destination' }).click();
|
||||
await Promise.all([postResponse, getResponse]);
|
||||
await expect(page.getByText('Connection successful', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('You have successfully created a sync destination')).toBeVisible();
|
||||
await basePage.dismissFlashMessages();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'test-gcp' })).toBeVisible();
|
||||
await expect(page.getByText('WIF', { exact: true })).toBeVisible();
|
||||
});
|
||||
86
ui/tests/helpers/sync/mocks.ts
Normal file
86
ui/tests/helpers/sync/mocks.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2016, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export const SYNC_DESTINATION_AWS_WIF_RESPONSE = {
|
||||
request_id: 'c2f069c7-bee5-ce3c-992c-59422c55f45a',
|
||||
lease_id: '',
|
||||
renewable: false,
|
||||
lease_duration: 0,
|
||||
data: {
|
||||
connection_details: {
|
||||
identity_token_audience: '*****',
|
||||
identity_token_ttl: 3600,
|
||||
region: 'us-east-1',
|
||||
role_arn: 'arn:aws:iam::111111111111:role/wif_test',
|
||||
},
|
||||
name: 'test-aws',
|
||||
options: {
|
||||
custom_tags: {},
|
||||
granularity_level: 'secret-path',
|
||||
secret_name_template: 'vault/{{ .MountAccessor }}/{{ .SecretPath }}',
|
||||
},
|
||||
type: 'aws-sm',
|
||||
uses_wif: true,
|
||||
},
|
||||
wrap_info: null,
|
||||
warnings: null,
|
||||
auth: null,
|
||||
mount_type: 'system',
|
||||
};
|
||||
|
||||
export const SYNC_DESTINATION_AZURE_WIF_RESPONSE = {
|
||||
request_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
lease_id: '',
|
||||
renewable: false,
|
||||
lease_duration: 0,
|
||||
data: {
|
||||
connection_details: {
|
||||
identity_token_audience: '*****',
|
||||
identity_token_ttl: 3600,
|
||||
key_vault_uri: 'https://test-keyvault.vault.azure.net',
|
||||
tenant_id: '11111111-1111-1111-1111-111111111111',
|
||||
client_id: 'test-client-id',
|
||||
},
|
||||
name: 'test-azure',
|
||||
options: {
|
||||
custom_tags: {},
|
||||
granularity_level: 'secret-path',
|
||||
secret_name_template: 'vault/{{ .MountAccessor }}/{{ .SecretPath }}',
|
||||
},
|
||||
type: 'azure-kv',
|
||||
uses_wif: true,
|
||||
},
|
||||
wrap_info: null,
|
||||
warnings: null,
|
||||
auth: null,
|
||||
mount_type: 'system',
|
||||
};
|
||||
|
||||
export const SYNC_DESTINATION_GCP_WIF_RESPONSE = {
|
||||
request_id: 'b2c3d4e5-f6a7-8901-bcde-f12345678901',
|
||||
lease_id: '',
|
||||
renewable: false,
|
||||
lease_duration: 0,
|
||||
data: {
|
||||
connection_details: {
|
||||
identity_token_audience: '*****',
|
||||
identity_token_ttl: 3600,
|
||||
project_id: 'test-gcp-project',
|
||||
service_account_email: 'test-sa@test-gcp-project.iam.gserviceaccount.com',
|
||||
},
|
||||
name: 'test-gcp',
|
||||
options: {
|
||||
custom_tags: {},
|
||||
granularity_level: 'secret-path',
|
||||
secret_name_template: 'vault/{{ .MountAccessor }}/{{ .SecretPath }}',
|
||||
},
|
||||
type: 'gcp-sm',
|
||||
uses_wif: true,
|
||||
},
|
||||
wrap_info: null,
|
||||
warnings: null,
|
||||
auth: null,
|
||||
mount_type: 'system',
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user