mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Compare commits
70 Commits
0d0dabe166
...
PROWLER-18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3cad0c11 | ||
|
|
054f85d7ba | ||
|
|
693a2e9694 | ||
|
|
34c84d1c02 | ||
|
|
0917817028 | ||
|
|
856f121947 | ||
|
|
3cc49beb45 | ||
|
|
271c7c4e01 | ||
|
|
f02fc07ed3 | ||
|
|
d762a3433a | ||
|
|
cd8b4dd2da | ||
|
|
3665435aa4 | ||
|
|
853f926fa1 | ||
|
|
834cef28a1 | ||
|
|
2409049c88 | ||
|
|
477de791a9 | ||
|
|
53607597b6 | ||
|
|
4250d4ee07 | ||
|
|
f26083772e | ||
|
|
a2e409d10e | ||
|
|
027ae6cd73 | ||
|
|
bc76a8cf25 | ||
|
|
e0bedb06d7 | ||
|
|
0b372477be | ||
|
|
670d9a8f87 | ||
|
|
f6f25d1191 | ||
|
|
bd1aac2527 | ||
|
|
cb1284e6e7 | ||
|
|
74580291a7 | ||
|
|
83540472e4 | ||
|
|
1cec011d8d | ||
|
|
5af7c950ac | ||
|
|
642efecd37 | ||
|
|
ccd8908128 | ||
|
|
c0a82910ad | ||
|
|
a004a73891 | ||
|
|
fccd2c3b9c | ||
|
|
e8712359c7 | ||
|
|
1ea859f606 | ||
|
|
d13182288e | ||
|
|
85d9411283 | ||
|
|
a500339138 | ||
|
|
4a0f0ba5bb | ||
|
|
5384d30fd5 | ||
|
|
a6121396ca | ||
|
|
d17d519a3e | ||
|
|
71f5ac5165 | ||
|
|
f15fdfc642 | ||
|
|
94d5322f16 | ||
|
|
4920f84d75 | ||
|
|
7b321c8cd1 | ||
|
|
1212356db3 | ||
|
|
13436613d6 | ||
|
|
b23b083092 | ||
|
|
d63ae0e40f | ||
|
|
5d86aacb2a | ||
|
|
0d088eca13 | ||
|
|
055964aff3 | ||
|
|
447d754b49 | ||
|
|
761563472b | ||
|
|
5e3db29de7 | ||
|
|
d8ca60a4ab | ||
|
|
cef7fcc24b | ||
|
|
fcf42937aa | ||
|
|
2c9d8ad8ea | ||
|
|
f424342e7e | ||
|
|
9b7e4f59e1 | ||
|
|
d21222aa3a | ||
|
|
bdbb2fad78 | ||
|
|
cf7b66101c |
24
.github/workflows/ui-e2e-tests.yml
vendored
24
.github/workflows/ui-e2e-tests.yml
vendored
@@ -10,6 +10,7 @@ on:
|
||||
- 'ui/**'
|
||||
|
||||
jobs:
|
||||
|
||||
e2e-tests:
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
@@ -33,10 +34,29 @@ jobs:
|
||||
E2E_M365_SECRET_ID: ${{ secrets.E2E_M365_SECRET_ID }}
|
||||
E2E_M365_TENANT_ID: ${{ secrets.E2E_M365_TENANT_ID }}
|
||||
E2E_M365_CERTIFICATE_CONTENT: ${{ secrets.E2E_M365_CERTIFICATE_CONTENT }}
|
||||
E2E_NEW_PASSWORD: ${{ secrets.E2E_NEW_PASSWORD }}
|
||||
E2E_KUBERNETES_CONTEXT: 'kind-kind'
|
||||
E2E_KUBERNETES_KUBECONFIG_PATH: /home/runner/.kube/config
|
||||
E2E_NEW_USER_PASSWORD: ${{ secrets.E2E_NEW_USER_PASSWORD }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1
|
||||
with:
|
||||
cluster_name: kind
|
||||
- name: Modify kubeconfig
|
||||
run: |
|
||||
# Modify the kubeconfig to use the kind cluster server to https://kind-control-plane:6443
|
||||
# from worker service into docker-compose.yml
|
||||
kubectl config set-cluster kind-kind --server=https://kind-control-plane:6443
|
||||
kubectl config view
|
||||
- name: Add network kind to docker compose
|
||||
run: |
|
||||
# Add the network kind to the docker compose to interconnect to kind cluster
|
||||
yq -i '.networks.kind.external = true' docker-compose.yml
|
||||
# Add network kind to worker service and default network too
|
||||
yq -i '.services.worker.networks = ["kind","default"]' docker-compose.yml
|
||||
- name: Fix API data directory permissions
|
||||
run: docker run --rm -v $(pwd)/_data/api:/data alpine chown -R 1000:1000 /data
|
||||
- name: Start API services
|
||||
@@ -113,4 +133,4 @@ jobs:
|
||||
run: |
|
||||
echo "Shutting down services..."
|
||||
docker compose down -v || true
|
||||
echo "Cleanup completed"
|
||||
echo "Cleanup completed"
|
||||
@@ -23,6 +23,18 @@ export interface M365ProviderData {
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
// Kubernetes provider data
|
||||
export interface KubernetesProviderData {
|
||||
context: string;
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
// GCP provider data
|
||||
export interface GCPProviderData {
|
||||
projectId: string;
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
// AWS credential options
|
||||
export const AWS_CREDENTIAL_OPTIONS = {
|
||||
AWS_ROLE_ARN: "role",
|
||||
@@ -78,6 +90,34 @@ export interface M365ProviderCredential {
|
||||
certificateContent?: string;
|
||||
}
|
||||
|
||||
// Kubernetes credential options
|
||||
export const KUBERNETES_CREDENTIAL_OPTIONS = {
|
||||
KUBECONFIG_CONTENT: "kubeconfig"
|
||||
} as const;
|
||||
|
||||
// Kubernetes credential type
|
||||
type KubernetesCredentialType = (typeof KUBERNETES_CREDENTIAL_OPTIONS)[keyof typeof KUBERNETES_CREDENTIAL_OPTIONS];
|
||||
|
||||
// Kubernetes provider credential
|
||||
export interface KubernetesProviderCredential {
|
||||
type: KubernetesCredentialType;
|
||||
kubeconfigContent:string;
|
||||
}
|
||||
|
||||
// GCP credential options
|
||||
export const GCP_CREDENTIAL_OPTIONS = {
|
||||
GCP_SERVICE_ACCOUNT: "service_account"
|
||||
} as const;
|
||||
|
||||
// GCP credential type
|
||||
type GCPCredentialType = (typeof GCP_CREDENTIAL_OPTIONS)[keyof typeof GCP_CREDENTIAL_OPTIONS];
|
||||
|
||||
// GCP provider credential
|
||||
export interface GCPProviderCredential {
|
||||
type: GCPCredentialType;
|
||||
serviceAccountKey:string;
|
||||
}
|
||||
|
||||
// Providers page
|
||||
export class ProvidersPage extends BasePage {
|
||||
// Button to add a new cloud provider
|
||||
@@ -129,6 +169,15 @@ export class ProvidersPage extends BasePage {
|
||||
readonly m365TenantIdInput: Locator;
|
||||
readonly m365CertificateContentInput: Locator;
|
||||
|
||||
// Kubernetes provider form elements
|
||||
readonly kubernetesContextInput: Locator;
|
||||
readonly kubernetesKubeconfigContentInput: Locator;
|
||||
|
||||
// GCP provider form elements
|
||||
readonly gcpProjectIdInput: Locator;
|
||||
readonly gcpServiceAccountKeyInput: Locator;
|
||||
readonly gcpServiceAccountRadio: Locator;
|
||||
|
||||
// Delete button
|
||||
readonly deleteProviderConfirmationButton: Locator;
|
||||
|
||||
@@ -180,9 +229,15 @@ export class ProvidersPage extends BasePage {
|
||||
name: "Client Secret",
|
||||
});
|
||||
this.m365TenantIdInput = page.getByRole("textbox", { name: "Tenant ID" });
|
||||
this.m365CertificateContentInput = page.getByRole("textbox", {
|
||||
name: "Certificate Content",
|
||||
});
|
||||
this.m365CertificateContentInput = page.getByRole("textbox", { name: "Certificate Content" });
|
||||
|
||||
// Kubernetes provider form inputs
|
||||
this.kubernetesContextInput = page.getByRole("textbox", { name: "Context" });
|
||||
this.kubernetesKubeconfigContentInput = page.getByRole("textbox", { name: "Kubeconfig Content" });
|
||||
|
||||
// GCP provider form inputs
|
||||
this.gcpProjectIdInput = page.getByRole("textbox", { name: "Project ID" });
|
||||
this.gcpServiceAccountKeyInput = page.getByRole("textbox", { name: "Service Account Key" });
|
||||
|
||||
// Alias input
|
||||
this.aliasInput = page.getByRole("textbox", {
|
||||
@@ -220,6 +275,11 @@ export class ProvidersPage extends BasePage {
|
||||
name: /App Certificate Credentials/i,
|
||||
});
|
||||
|
||||
// Radios for selecting GCP credentials method
|
||||
this.gcpServiceAccountRadio = page.getByRole("radio", {
|
||||
name: /Service Account Key/i,
|
||||
});
|
||||
|
||||
// Inputs for IAM Role credentials
|
||||
this.roleArnInput = page.getByRole("textbox", { name: "Role ARN" });
|
||||
this.externalIdInput = page.getByRole("textbox", { name: "External ID" });
|
||||
@@ -271,6 +331,21 @@ export class ProvidersPage extends BasePage {
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectKubernetesProvider(): Promise<void> {
|
||||
// Select the Kubernetes provider
|
||||
|
||||
await this.kubernetesProviderRadio.click({ force: true });
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectGCPProvider(): Promise<void> {
|
||||
// Select the GCP provider
|
||||
|
||||
await this.gcpProviderRadio.click({ force: true });
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
|
||||
async fillAWSProviderDetails(data: AWSProviderData): Promise<void> {
|
||||
// Fill the AWS provider details
|
||||
|
||||
@@ -301,6 +376,27 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
}
|
||||
|
||||
async fillKubernetesProviderDetails(data: KubernetesProviderData): Promise<void> {
|
||||
// Fill the Kubernetes provider details
|
||||
|
||||
await this.kubernetesContextInput.fill(data.context);
|
||||
|
||||
if (data.alias) {
|
||||
await this.aliasInput.fill(data.alias);
|
||||
}
|
||||
}
|
||||
|
||||
async fillGCPProviderDetails(data: GCPProviderData): Promise<void> {
|
||||
// Fill the GCP provider details
|
||||
|
||||
await this.gcpProjectIdInput.fill(data.projectId);
|
||||
|
||||
if (data.alias) {
|
||||
|
||||
await this.aliasInput.fill(data.alias);
|
||||
}
|
||||
}
|
||||
|
||||
async clickNext(): Promise<void> {
|
||||
// The wizard interface may use different labels for its primary action button on each step.
|
||||
// This function determines which button to click depending on the current URL and page content.
|
||||
@@ -319,6 +415,7 @@ export class ProvidersPage extends BasePage {
|
||||
if (/\/providers\/add-credentials/.test(url)) {
|
||||
// Some UI implementations use "Save" instead of "Next" for primary action
|
||||
const saveBtn = this.saveButton;
|
||||
|
||||
if (await saveBtn.count()) {
|
||||
await saveBtn.click();
|
||||
await this.waitForPageLoad();
|
||||
@@ -334,6 +431,7 @@ export class ProvidersPage extends BasePage {
|
||||
|
||||
// If on the "test-connection" step, click the "Launch scan" button
|
||||
if (/\/providers\/test-connection/.test(url)) {
|
||||
|
||||
const buttonByText = this.page
|
||||
.locator("button")
|
||||
.filter({ hasText: "Launch scan" });
|
||||
@@ -361,6 +459,7 @@ export class ProvidersPage extends BasePage {
|
||||
.catch(() => false);
|
||||
if (isErrorVisible) {
|
||||
const errorText = await errorMessage.textContent();
|
||||
|
||||
throw new Error(
|
||||
`Test connection failed with error: ${errorText?.trim() || "Unknown error"}`,
|
||||
);
|
||||
@@ -368,11 +467,12 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
} catch (error) {
|
||||
// If timeout or other error, check if error message is present
|
||||
const isErrorVisible = await errorMessage
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
const isErrorVisible = await errorMessage.isVisible().catch(() => false);
|
||||
|
||||
if (isErrorVisible) {
|
||||
|
||||
const errorText = await errorMessage.textContent();
|
||||
|
||||
throw new Error(
|
||||
`Test connection failed with error: ${errorText?.trim() || "Unknown error"}`,
|
||||
);
|
||||
@@ -416,6 +516,7 @@ export class ProvidersPage extends BasePage {
|
||||
// Ensure we are on the add-credentials page where the selector exists
|
||||
|
||||
await expect(this.page).toHaveURL(/\/providers\/add-credentials/);
|
||||
|
||||
if (type === AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN) {
|
||||
await this.roleCredentialsRadio.click({ force: true });
|
||||
} else if (type === AWS_CREDENTIAL_OPTIONS.AWS_CREDENTIALS) {
|
||||
@@ -431,6 +532,7 @@ export class ProvidersPage extends BasePage {
|
||||
// Ensure we are on the add-credentials page where the selector exists
|
||||
|
||||
await expect(this.page).toHaveURL(/\/providers\/add-credentials/);
|
||||
|
||||
if (type === M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS) {
|
||||
await this.m365StaticCredentialsRadio.click({ force: true });
|
||||
} else if (type === M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS) {
|
||||
@@ -442,6 +544,19 @@ export class ProvidersPage extends BasePage {
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectGCPCredentialsType(type: GCPCredentialType): Promise<void> {
|
||||
// Ensure we are on the add-credentials page where the selector exists
|
||||
|
||||
await expect(this.page).toHaveURL(/\/providers\/add-credentials/);
|
||||
if (type === GCP_CREDENTIAL_OPTIONS.GCP_SERVICE_ACCOUNT) {
|
||||
await this.gcpServiceAccountRadio.click({ force: true });
|
||||
} else {
|
||||
throw new Error(`Invalid GCP credential type: ${type}`);
|
||||
}
|
||||
// Wait for the page to load
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async fillRoleCredentials(credentials: AWSProviderCredential): Promise<void> {
|
||||
// Fill the role credentials form
|
||||
|
||||
@@ -525,6 +640,22 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
}
|
||||
|
||||
async fillKubernetesCredentials(credentials: KubernetesProviderCredential): Promise<void> {
|
||||
// Fill the Kubernetes credentials form
|
||||
|
||||
if (credentials.kubeconfigContent) {
|
||||
await this.kubernetesKubeconfigContentInput.fill(credentials.kubeconfigContent);
|
||||
}
|
||||
}
|
||||
|
||||
async fillGCPServiceAccountKeyCredentials(credentials: GCPProviderCredential): Promise<void> {
|
||||
// Fill the GCP credentials form
|
||||
|
||||
if (credentials.serviceAccountKey) {
|
||||
await this.gcpServiceAccountKeyInput.fill(credentials.serviceAccountKey);
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPageLoaded(): Promise<void> {
|
||||
// Verify the providers page is loaded
|
||||
|
||||
@@ -565,6 +696,21 @@ export class ProvidersPage extends BasePage {
|
||||
await expect(this.m365CertificateContentInput).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyKubernetesCredentialsPageLoaded(): Promise<void> {
|
||||
// Verify the Kubernetes credentials page is loaded
|
||||
|
||||
await expect(this.page).toHaveTitle(/Prowler/);
|
||||
await expect(this.kubernetesContextInput).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyGCPServiceAccountPageLoaded(): Promise<void> {
|
||||
// Verify the GCP service account page is loaded
|
||||
|
||||
await expect(this.page).toHaveTitle(/Prowler/);
|
||||
await expect(this.gcpServiceAccountKeyInput).toBeVisible();
|
||||
}
|
||||
|
||||
|
||||
async verifyLaunchScanPageLoaded(): Promise<void> {
|
||||
// Verify the launch scan page is loaded
|
||||
|
||||
@@ -575,6 +721,7 @@ export class ProvidersPage extends BasePage {
|
||||
const launchScanButton = this.page
|
||||
.locator("button")
|
||||
.filter({ hasText: "Launch scan" });
|
||||
|
||||
await expect(launchScanButton).toBeVisible();
|
||||
}
|
||||
|
||||
@@ -598,6 +745,7 @@ export class ProvidersPage extends BasePage {
|
||||
|
||||
// Verify the number of matching rows is 1
|
||||
const count = await matchingRows.count();
|
||||
|
||||
if (count !== 1) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -611,6 +759,7 @@ export class ProvidersPage extends BasePage {
|
||||
|
||||
// Find and use the search input to filter the table
|
||||
const searchInput = this.page.getByPlaceholder(/search|filter/i);
|
||||
|
||||
await expect(searchInput).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Clear and search for the specific provider
|
||||
@@ -632,6 +781,7 @@ export class ProvidersPage extends BasePage {
|
||||
// Helper function to check if a row is the "No results" row
|
||||
const isNoResultsRow = async (row: Locator): Promise<boolean> => {
|
||||
const text = await row.textContent();
|
||||
|
||||
return text?.includes("No results") || text?.includes("No data") || false;
|
||||
};
|
||||
|
||||
@@ -688,11 +838,8 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
// Find and click the action button (last cell = actions column)
|
||||
const actionButton = targetRow
|
||||
.locator("td")
|
||||
.last()
|
||||
.locator("button")
|
||||
.first();
|
||||
const actionButton = targetRow.locator("td").last().locator("button").first();
|
||||
|
||||
await expect(actionButton).toBeVisible({ timeout: 5000 });
|
||||
await actionButton.click();
|
||||
|
||||
@@ -700,13 +847,13 @@ export class ProvidersPage extends BasePage {
|
||||
const deleteMenuItem = this.page.getByRole("menuitem", {
|
||||
name: /delete.*provider/i,
|
||||
});
|
||||
|
||||
await expect(deleteMenuItem).toBeVisible({ timeout: 5000 });
|
||||
await deleteMenuItem.click();
|
||||
|
||||
// Wait for confirmation modal to appear
|
||||
const modal = this.page
|
||||
.locator('[role="dialog"], .modal, [data-testid*="modal"]')
|
||||
.first();
|
||||
const modal = this.page.locator('[role="dialog"], .modal, [data-testid*="modal"]').first();
|
||||
|
||||
await expect(modal).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Find and click the delete confirmation button
|
||||
|
||||
@@ -265,3 +265,118 @@
|
||||
- Provider cleanup performed before each test to ensure clean state
|
||||
- Requires valid Microsoft 365 tenant with certificate-based authentication
|
||||
- Certificate must be properly configured and have sufficient permissions for security scanning
|
||||
|
||||
---
|
||||
|
||||
## Test Case: `PROVIDER-E2E-006` - Add Kubernetes Provider with Kubeconfig Content
|
||||
|
||||
**Priority:** `critical`
|
||||
|
||||
**Tags:**
|
||||
|
||||
- type → @e2e, @serial
|
||||
- feature → @providers
|
||||
- provider → @kubernetes
|
||||
|
||||
**Description/Objective:** Validates the complete flow of adding a new Kubernetes provider using kubeconfig content authentication
|
||||
|
||||
**Preconditions:**
|
||||
|
||||
- Admin user authentication required (admin.auth.setup setup)
|
||||
- Environment variables configured: E2E_KUBERNETES_CONTEXT, E2E_KUBERNETES_KUBECONFIG_PATH
|
||||
- Kubeconfig file must exist at the specified path
|
||||
- Remove any existing provider with the same Context before starting the test
|
||||
- This test must be run serially and never in parallel with other tests, as it requires the Context not to be already registered beforehand.
|
||||
|
||||
### Flow Steps:
|
||||
|
||||
1. Navigate to providers page
|
||||
2. Click "Add Provider" button
|
||||
3. Select Kubernetes provider type
|
||||
4. Fill provider details (context and alias)
|
||||
5. Verify credentials page is loaded
|
||||
6. Fill Kubernetes credentials (kubeconfig content)
|
||||
7. Launch initial scan
|
||||
8. Verify redirect to provider management page
|
||||
|
||||
### Expected Result:
|
||||
|
||||
- Kubernetes provider successfully added with kubeconfig content
|
||||
- Initial scan launched successfully
|
||||
- User redirected to provider details page
|
||||
|
||||
### Key verification points:
|
||||
|
||||
- Provider page loads correctly
|
||||
- Connect account page displays Kubernetes option
|
||||
- Provider details form accepts context and alias
|
||||
- Credentials page loads with kubeconfig content field
|
||||
- Kubeconfig content is properly filled in the correct field
|
||||
- Launch scan page appears
|
||||
- Successful redirect to provider page after scan launch
|
||||
|
||||
### Notes:
|
||||
|
||||
- Test uses environment variables for Kubernetes context and kubeconfig file path
|
||||
- Kubeconfig content is read from file and used for authentication
|
||||
- Provider cleanup performed before each test to ensure clean state
|
||||
- Requires valid Kubernetes cluster with accessible kubeconfig
|
||||
- Kubeconfig must have sufficient permissions for security scanning
|
||||
- Test validates that kubeconfig content goes to the correct field (not the context field)
|
||||
|
||||
---
|
||||
|
||||
## Test Case: `PROVIDER-E2E-007` - Add GCP Provider with Service Account Key
|
||||
|
||||
**Priority:** `critical`
|
||||
|
||||
**Tags:**
|
||||
|
||||
- type → @e2e, @serial
|
||||
- feature → @providers
|
||||
- provider → @gcp
|
||||
|
||||
**Description/Objective:** Validates the complete flow of adding a new GCP provider using service account key authentication
|
||||
|
||||
**Preconditions:**
|
||||
|
||||
- Admin user authentication required (admin.auth.setup setup)
|
||||
- Environment variables configured: E2E_GCP_PROJECT_ID, E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY
|
||||
- Remove any existing provider with the same Project ID before starting the test
|
||||
- This test must be run serially and never in parallel with other tests, as it requires the Project ID not to be already registered beforehand.
|
||||
|
||||
### Flow Steps:
|
||||
|
||||
1. Navigate to providers page
|
||||
2. Click "Add Provider" button
|
||||
3. Select GCP provider type
|
||||
4. Fill provider details (project ID and alias)
|
||||
5. Select service account credentials type
|
||||
6. Fill GCP service account key credentials
|
||||
7. Launch initial scan
|
||||
8. Verify redirect to provider management page
|
||||
|
||||
### Expected Result:
|
||||
|
||||
- GCP provider successfully added with service account key
|
||||
- Initial scan launched successfully
|
||||
- User redirected to provider details page
|
||||
|
||||
### Key verification points:
|
||||
|
||||
- Provider page loads correctly
|
||||
- Connect account page displays GCP option
|
||||
- Provider details form accepts project ID and alias
|
||||
- Service account credentials page loads with service account key field
|
||||
- Service account key is properly filled in the correct field
|
||||
- Launch scan page appears
|
||||
- Successful redirect to provider page after scan launch
|
||||
|
||||
### Notes:
|
||||
|
||||
- Test uses environment variables for GCP project ID and service account key
|
||||
- Service account key is provided as base64 encoded JSON content
|
||||
- Provider cleanup performed before each test to ensure clean state
|
||||
- Requires valid GCP project with service account having appropriate permissions
|
||||
- Service account must have sufficient permissions for security scanning
|
||||
- Test validates that service account key goes to the correct field
|
||||
|
||||
@@ -10,7 +10,14 @@ import {
|
||||
M365ProviderData,
|
||||
M365ProviderCredential,
|
||||
M365_CREDENTIAL_OPTIONS,
|
||||
KubernetesProviderData,
|
||||
KubernetesProviderCredential,
|
||||
KUBERNETES_CREDENTIAL_OPTIONS,
|
||||
GCPProviderData,
|
||||
GCPProviderCredential,
|
||||
GCP_CREDENTIAL_OPTIONS,
|
||||
} from "./providers-page";
|
||||
import fs from "fs";
|
||||
import { ScansPage } from "../scans/scans-page";
|
||||
|
||||
test.describe("Add Provider", () => {
|
||||
@@ -440,4 +447,209 @@ test.describe("Add Provider", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test.describe.serial("Add Kubernetes Provider", () => {
|
||||
// Providers page object
|
||||
let providersPage: ProvidersPage;
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const context = process.env.E2E_KUBERNETES_CONTEXT;
|
||||
const kubeconfigPath = process.env.E2E_KUBERNETES_KUBECONFIG_PATH;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!context || !kubeconfigPath) {
|
||||
throw new Error(
|
||||
"E2E_KUBERNETES_CONTEXT and E2E_KUBERNETES_KUBECONFIG_PATH environment variables are not set",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await providersPage.deleteProviderIfExists(context);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
test.use({ storageState: "playwright/.auth/admin_user.json" });
|
||||
|
||||
test(
|
||||
"should add a new Kubernetes provider with kubeconfig context",
|
||||
{
|
||||
tag: [
|
||||
"@critical",
|
||||
"@e2e",
|
||||
"@providers",
|
||||
"@kubernetes",
|
||||
"@serial",
|
||||
"@PROVIDER-E2E-006",
|
||||
],
|
||||
},
|
||||
async ({ page }) => {
|
||||
// Verify kubeconfig file exists
|
||||
if (!fs.existsSync(kubeconfigPath)) {
|
||||
throw new Error(`Kubeconfig file not found at ${kubeconfigPath}`);
|
||||
}
|
||||
|
||||
// Read kubeconfig content from file
|
||||
let kubeconfigContent: string;
|
||||
try {
|
||||
kubeconfigContent = fs.readFileSync(kubeconfigPath, "utf8");
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to read kubeconfig file at ${kubeconfigPath}: ${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare test data for Kubernetes provider
|
||||
const kubernetesProviderData: KubernetesProviderData = {
|
||||
context: context,
|
||||
alias: "Test E2E Kubernetes Account - Kubeconfig Context",
|
||||
};
|
||||
|
||||
// Prepare static credentials
|
||||
const kubernetesCredentials: KubernetesProviderCredential = {
|
||||
type: KUBERNETES_CREDENTIAL_OPTIONS.KUBECONFIG_CONTENT,
|
||||
kubeconfigContent: kubeconfigContent,
|
||||
};
|
||||
|
||||
// Navigate to providers page
|
||||
await providersPage.goto();
|
||||
await providersPage.verifyPageLoaded();
|
||||
|
||||
// Start adding new provider
|
||||
await providersPage.clickAddProvider();
|
||||
await providersPage.verifyConnectAccountPageLoaded();
|
||||
|
||||
// Select Kubernetes provider
|
||||
await providersPage.selectKubernetesProvider();
|
||||
|
||||
// Fill provider details
|
||||
await providersPage.fillKubernetesProviderDetails(
|
||||
kubernetesProviderData,
|
||||
);
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Verify credentials page is loaded
|
||||
await providersPage.verifyKubernetesCredentialsPageLoaded();
|
||||
|
||||
// Fill static credentials details
|
||||
await providersPage.fillKubernetesCredentials(kubernetesCredentials);
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Launch scan
|
||||
await providersPage.verifyLaunchScanPageLoaded();
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Wait for redirect to provider page
|
||||
scansPage = new ScansPage(page);
|
||||
await scansPage.verifyPageLoaded();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test.describe.serial("Add GCP Provider", () => {
|
||||
// Providers page object
|
||||
let providersPage: ProvidersPage;
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const projectId = process.env.E2E_GCP_PROJECT_ID;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!projectId ) {
|
||||
throw new Error(
|
||||
"E2E_GCP_PROJECT_ID environment variable is not set",
|
||||
);
|
||||
}
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await providersPage.deleteProviderIfExists(projectId);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
test.use({ storageState: "playwright/.auth/admin_user.json" });
|
||||
|
||||
test(
|
||||
"should add a new GCP provider with service account key",
|
||||
{
|
||||
tag: [
|
||||
"@critical",
|
||||
"@e2e",
|
||||
"@providers",
|
||||
"@gcp",
|
||||
"@serial",
|
||||
"@PROVIDER-E2E-007",
|
||||
],
|
||||
},
|
||||
async ({ page }) => {
|
||||
// Validate required environment variables
|
||||
const serviceAccountKeyB64 = process.env.E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY;
|
||||
|
||||
// Verify service account key is base64 encoded
|
||||
if (!serviceAccountKeyB64) {
|
||||
throw new Error("E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY environment variable is not set");
|
||||
}
|
||||
|
||||
// Decode service account key from base64
|
||||
const serviceAccountKey = Buffer.from(serviceAccountKeyB64, 'base64').toString('utf8');
|
||||
|
||||
// Verify service account key is valid JSON
|
||||
if (!JSON.parse(serviceAccountKey)) {
|
||||
throw new Error("Invalid service account key format");
|
||||
}
|
||||
|
||||
// Prepare test data for GCP provider
|
||||
const gcpProviderData: GCPProviderData = {
|
||||
projectId: projectId,
|
||||
alias: "Test E2E GCP Account - Service Account Key",
|
||||
};
|
||||
|
||||
// Prepare static credentials
|
||||
const gcpCredentials: GCPProviderCredential = {
|
||||
type: GCP_CREDENTIAL_OPTIONS.GCP_SERVICE_ACCOUNT,
|
||||
serviceAccountKey: serviceAccountKey
|
||||
};
|
||||
|
||||
// Navigate to providers page
|
||||
await providersPage.goto();
|
||||
await providersPage.verifyPageLoaded();
|
||||
|
||||
// Start adding new provider
|
||||
await providersPage.clickAddProvider();
|
||||
await providersPage.verifyConnectAccountPageLoaded();
|
||||
|
||||
// Select M365 provider
|
||||
await providersPage.selectGCPProvider();
|
||||
|
||||
// Fill provider details
|
||||
await providersPage.fillGCPProviderDetails(gcpProviderData);
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Select static credentials type
|
||||
await providersPage.selectGCPCredentialsType(
|
||||
GCP_CREDENTIAL_OPTIONS.GCP_SERVICE_ACCOUNT,
|
||||
);
|
||||
|
||||
// Verify GCP service account page is loaded
|
||||
await providersPage.verifyGCPServiceAccountPageLoaded();
|
||||
|
||||
// Fill static service account key details
|
||||
await providersPage.fillGCPServiceAccountKeyCredentials(gcpCredentials);
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Launch scan
|
||||
await providersPage.verifyLaunchScanPageLoaded();
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Wait for redirect to scan page
|
||||
scansPage = new ScansPage(page);
|
||||
await scansPage.verifyPageLoaded();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
**Preconditions:**
|
||||
- Application is running, email domain & password is acceptable for sign-up.
|
||||
- No existing data in Prowler is required; the test can run on a clean state.
|
||||
- `E2E_NEW_PASSWORD` environment variable must be set with a valid password for the test.
|
||||
- `E2E_NEW_USER_PASSWORD` environment variable must be set with a valid password for the test.
|
||||
|
||||
### Flow Steps:
|
||||
1. Navigate to the Sign up page.
|
||||
@@ -38,6 +38,6 @@
|
||||
|
||||
### Notes:
|
||||
- Test data uses a random base36 suffix to avoid collisions with email.
|
||||
- The test requires the `E2E_NEW_PASSWORD` environment variable to be set before running.
|
||||
- The test requires the `E2E_NEW_USER_PASSWORD` environment variable to be set before running.
|
||||
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ test.describe("Sign Up Flow", () => {
|
||||
"should register a new user successfully",
|
||||
{ tag: ["@critical", "@e2e", "@signup", "@SIGNUP-E2E-001"] },
|
||||
async ({ page }) => {
|
||||
const password = process.env.E2E_NEW_PASSWORD;
|
||||
const password = process.env.E2E_NEW_USER_PASSWORD;
|
||||
|
||||
if (!password) {
|
||||
throw new Error("E2E_NEW_PASSWORD environment variable is not set");
|
||||
throw new Error("E2E_NEW_USER_PASSWORD environment variable is not set");
|
||||
}
|
||||
|
||||
const signUpPage = new SignUpPage(page);
|
||||
|
||||
Reference in New Issue
Block a user