mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
396 lines
18 KiB
TypeScript
396 lines
18 KiB
TypeScript
import type { IntegrationType } from "../types/integrations";
|
|
import {
|
|
PROVIDER_WIZARD_STEP,
|
|
type ProviderWizardStep,
|
|
} from "../types/provider-wizard";
|
|
|
|
// Documentation URLs
|
|
export const DOCS_URLS = {
|
|
FINDINGS_ANALYSIS:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-app#step-8:-analyze-the-findings",
|
|
FINDINGS_INGESTION:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings",
|
|
FINDINGS_TRIAGE:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-app-findings-triage",
|
|
AWS_ORGANIZATIONS:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-cloud-aws-organizations",
|
|
AZURE_ORGANIZATIONS:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-cloud-azure-management-groups",
|
|
GCP_ORGANIZATIONS:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-cloud-gcp-organizations",
|
|
ALERTS: "https://docs.prowler.com/user-guide/tutorials/prowler-app-alerts",
|
|
SCAN_CONFIGURATION:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-app-scan-configuration",
|
|
ATTACK_PATHS_CUSTOM_QUERIES:
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths#writing-custom-opencypher-queries",
|
|
AI_AGENTS: "https://docs.prowler.com/user-guide/ai-agents/",
|
|
} as const;
|
|
|
|
// Prowler Hub — the public catalog of Prowler artifacts (checks, compliance,
|
|
// attack paths). Every built-in Attack Paths query has a page keyed by its
|
|
// query id, e.g. https://hub.prowler.com/attack-paths/aws-sts-privesc-assume-role
|
|
export const PROWLER_HUB_URL = "https://hub.prowler.com";
|
|
|
|
export const getAttackPathHubUrl = (queryId: string): string =>
|
|
`${PROWLER_HUB_URL}/attack-paths/${encodeURIComponent(queryId)}`;
|
|
|
|
// CloudFormation template URL for the ProwlerScan role.
|
|
// Also used (URL-encoded) as the templateURL param in the quick-create links
|
|
// built by getAWSCredentialsTemplateLinks and getAWSOrgDeploymentQuickLink below.
|
|
export const PROWLER_CF_TEMPLATE_URL =
|
|
"https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/aws/cloudformation/prowler-scan-role.yml";
|
|
|
|
// Prowler Cloud billing/subscription management page.
|
|
export const BILLING_URL = "https://cloud.prowler.com/billing";
|
|
|
|
// Base URL for the CloudFormation "quick create stack" console flow.
|
|
// Hardcoded to us-east-1 because the public template is hosted for that flow.
|
|
const CF_QUICKCREATE_BASE_URL =
|
|
"https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate";
|
|
|
|
// Deep links that open each provider's cloud console with the credential
|
|
// creation form pre-filled with the exact permissions, scopes and name
|
|
// Prowler needs. The full 16-provider audit (which providers support this and
|
|
// which do not) lives in the PROWLER-2187 PR description; keep both in sync
|
|
// when adding or removing entries here.
|
|
// AWS has its own CloudFormation quick-create link built in
|
|
// `getAWSCredentialsTemplateLinks` below.
|
|
export const PRECONFIGURED_CREDENTIAL_URLS = {
|
|
// Opens the Cloudflare "Create Custom Token" form under the user profile
|
|
// pre-filled with the four read-only scopes Prowler needs
|
|
// (`Account Settings`, `Zone`, `Zone Settings`, `DNS`) and the token name.
|
|
// Kept in sync with the "User API Token" URL published in
|
|
// docs/user-guide/providers/cloudflare/authentication.mdx.
|
|
CLOUDFLARE_API_TOKEN_USER:
|
|
"https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22read%22%7D%5D&accountId=%2A&zoneId=all&name=Prowler%20Security%20Scanner",
|
|
// Opens the GitHub fine-grained PAT creation form pre-filled with the four
|
|
// read-only permissions Prowler needs to scan a user's own repositories.
|
|
// Kept in sync with the "user repositories" URL published in
|
|
// docs/user-guide/providers/github/authentication.mdx.
|
|
GITHUB_PERSONAL_ACCESS_TOKEN_USER:
|
|
"https://github.com/settings/personal-access-tokens/new?name=Prowler+Security+Scanner&description=Fine-grained+PAT+for+Prowler+security+scanning&expires_in=90&administration=read&contents=read&vulnerability_alerts=read&emails=read",
|
|
} as const;
|
|
|
|
// Builds the account-owned Cloudflare API Token URL for the given account.
|
|
// Uses the dashboard router pattern (`?to=/<accountId>/api-tokens&...`)
|
|
// published in docs/user-guide/providers/cloudflare/authentication.mdx, with
|
|
// the account id substituted in place of the docs' `:account` placeholder to
|
|
// avoid ambiguity when the user is signed into multiple accounts. Navigating
|
|
// directly to `/<accountId>/api-tokens/create` does NOT pre-fill the form —
|
|
// Cloudflare only reads the pre-fill params when they arrive via the router.
|
|
// Same four read-only scopes as the user token URL.
|
|
export const buildCloudflareAccountOwnedApiTokenUrl = (
|
|
accountId: string,
|
|
): string => {
|
|
// Cloudflare's router expects the `to=` value with unencoded slashes; using
|
|
// URLSearchParams would percent-encode them and break the redirect, so we
|
|
// assemble the query string manually and only encode the pieces that need
|
|
// it.
|
|
const encodedAccountId = encodeURIComponent(accountId);
|
|
const permissionGroupKeys = encodeURIComponent(
|
|
JSON.stringify([
|
|
{ key: "account_settings", type: "read" },
|
|
{ key: "zone", type: "read" },
|
|
{ key: "zone_settings", type: "read" },
|
|
{ key: "dns", type: "read" },
|
|
]),
|
|
);
|
|
const name = encodeURIComponent("Prowler Security Scanner");
|
|
return `https://dash.cloudflare.com/?to=/${encodedAccountId}/api-tokens&permissionGroupKeys=${permissionGroupKeys}&name=${name}`;
|
|
};
|
|
|
|
// Builds the organization-scoped GitHub fine-grained PAT URL. GitHub validates
|
|
// permissions against the token's Resource Owner and only surfaces
|
|
// `organization_administration` and `members` when it is an organization, so
|
|
// we pin the owner via `target_name` and skip account-only permissions
|
|
// (`emails`) that the docs' org template does not request. Kept in sync with
|
|
// the "organization scanning" URL published in
|
|
// docs/user-guide/providers/github/authentication.mdx.
|
|
export const buildGitHubPersonalAccessTokenOrgUrl = (
|
|
targetName: string,
|
|
): string => {
|
|
const params = new URLSearchParams({
|
|
name: "Prowler Security Scanner",
|
|
description: "Fine-grained PAT for Prowler organization security scanning",
|
|
expires_in: "90",
|
|
target_name: targetName,
|
|
administration: "read",
|
|
contents: "read",
|
|
vulnerability_alerts: "read",
|
|
organization_administration: "read",
|
|
members: "read",
|
|
});
|
|
return `https://github.com/settings/personal-access-tokens/new?${params.toString()}`;
|
|
};
|
|
|
|
export interface AWSOrgDeploymentQuickLinkParams {
|
|
externalId: string;
|
|
organizationalUnitId: string;
|
|
deployFromDelegatedAdmin?: boolean;
|
|
}
|
|
|
|
const buildCloudFormationQuickCreateLink = (
|
|
parameters: Record<string, string>,
|
|
): string => {
|
|
const searchParams = new URLSearchParams({
|
|
templateURL: PROWLER_CF_TEMPLATE_URL,
|
|
stackName: "Prowler",
|
|
...parameters,
|
|
});
|
|
|
|
return `${CF_QUICKCREATE_BASE_URL}?${searchParams.toString()}`;
|
|
};
|
|
|
|
// Shortlinks are used for all wizard steps except credentials so link
|
|
// ownership stays with the docs/marketing team: they can retarget
|
|
// destinations from the shortener panel without a UI PR.
|
|
const PROVIDER_DOCS_SHORTLINK: Record<string, string> = {
|
|
aws: "https://goto.prowler.com/provider-aws",
|
|
azure: "https://goto.prowler.com/provider-azure",
|
|
m365: "https://goto.prowler.com/provider-m365",
|
|
gcp: "https://goto.prowler.com/provider-gcp",
|
|
kubernetes: "https://goto.prowler.com/provider-k8s",
|
|
github: "https://goto.prowler.com/provider-github",
|
|
iac: "https://goto.prowler.com/provider-iac",
|
|
image: "https://goto.prowler.com/provider-image",
|
|
oraclecloud: "https://goto.prowler.com/provider-oraclecloud",
|
|
mongodbatlas: "https://goto.prowler.com/provider-mongodbatlas",
|
|
alibabacloud: "https://goto.prowler.com/provider-alibabacloud",
|
|
cloudflare: "https://goto.prowler.com/provider-cloudflare",
|
|
openstack: "https://goto.prowler.com/provider-openstack",
|
|
googleworkspace: "https://goto.prowler.com/provider-googleworkspace",
|
|
vercel: "https://goto.prowler.com/provider-vercel",
|
|
okta: "https://goto.prowler.com/provider-okta",
|
|
};
|
|
|
|
// Default target for the credentials step: the section of the provider's
|
|
// getting-started page that introduces the credentials flow. The getting-
|
|
// started page keeps the user in the same mental model as the wizard, and
|
|
// each section already links to `authentication.mdx` for readers who need
|
|
// deeper detail. That indirection is intentional — we do NOT deep-link into
|
|
// `authentication.mdx` from the wizard, otherwise the user is jumped into
|
|
// low-level docs before they have the context to make sense of them.
|
|
const PROVIDER_CREDENTIALS_STEP_DOCS_URL: Record<string, string> = {
|
|
aws: "https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#step-3-set-up-aws-authentication",
|
|
azure:
|
|
"https://docs.prowler.com/user-guide/providers/azure/getting-started-azure#step-3-add-credentials-to-prowler-cloud",
|
|
m365: "https://docs.prowler.com/user-guide/providers/microsoft365/getting-started-m365#step-3-choose-and-provide-authentication",
|
|
gcp: "https://docs.prowler.com/user-guide/providers/gcp/getting-started-gcp#step-3-set-up-gcp-authentication",
|
|
kubernetes:
|
|
"https://docs.prowler.com/user-guide/providers/kubernetes/getting-started-k8s#step-2-configure-kubernetes-authentication",
|
|
github:
|
|
"https://docs.prowler.com/user-guide/providers/github/getting-started-github#step-3-choose-authentication-method",
|
|
iac: "https://docs.prowler.com/user-guide/providers/iac/getting-started-iac#step-2-enter-authentication-details",
|
|
image:
|
|
"https://docs.prowler.com/user-guide/providers/image/getting-started-image#step-2-enter-authentication-and-scan-filters",
|
|
oraclecloud:
|
|
"https://docs.prowler.com/user-guide/providers/oci/getting-started-oci#step-3-add-oci-api-key-credentials",
|
|
mongodbatlas:
|
|
"https://docs.prowler.com/user-guide/providers/mongodbatlas/getting-started-mongodbatlas#step-2-provide-api-credentials",
|
|
alibabacloud:
|
|
"https://docs.prowler.com/user-guide/providers/alibabacloud/getting-started-alibabacloud#step-3-choose-and-provide-authentication",
|
|
cloudflare:
|
|
"https://docs.prowler.com/user-guide/providers/cloudflare/getting-started-cloudflare#step-3-choose-and-provide-authentication",
|
|
openstack:
|
|
"https://docs.prowler.com/user-guide/providers/openstack/getting-started-openstack#step-2-provide-credentials",
|
|
googleworkspace:
|
|
"https://docs.prowler.com/user-guide/providers/googleworkspace/getting-started-googleworkspace#step-3-provide-credentials",
|
|
vercel:
|
|
"https://docs.prowler.com/user-guide/providers/vercel/getting-started-vercel#step-2-provide-credentials",
|
|
okta: "https://docs.prowler.com/user-guide/providers/okta/getting-started-okta#step-2-provide-credentials",
|
|
};
|
|
|
|
// When the user has picked a specific auth method inside the credentials
|
|
// step, jump directly to that method's subsection in the getting-started
|
|
// page. Only providers whose docs have a heading per method are listed —
|
|
// GCP and GitHub render their methods inside a Mintlify `<Tabs>` component
|
|
// so a per-method anchor isn't available today; they fall back to the
|
|
// general step URL above.
|
|
const PROVIDER_CREDENTIALS_METHOD_DOCS_URL: Record<
|
|
string,
|
|
Record<string, string>
|
|
> = {
|
|
aws: {
|
|
role: "https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#assume-role-recommended",
|
|
credentials:
|
|
"https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#credentials-static-access-keys",
|
|
},
|
|
m365: {
|
|
app_certificate:
|
|
"https://docs.prowler.com/user-guide/providers/microsoft365/getting-started-m365#application-certificate-authentication-recommended",
|
|
app_client_secret:
|
|
"https://docs.prowler.com/user-guide/providers/microsoft365/getting-started-m365#application-client-secret-authentication",
|
|
},
|
|
alibabacloud: {
|
|
role: "https://docs.prowler.com/user-guide/providers/alibabacloud/getting-started-alibabacloud#ram-role-assumption-recommended",
|
|
credentials:
|
|
"https://docs.prowler.com/user-guide/providers/alibabacloud/getting-started-alibabacloud#credentials-static-access-keys",
|
|
},
|
|
cloudflare: {
|
|
api_token:
|
|
"https://docs.prowler.com/user-guide/providers/cloudflare/getting-started-cloudflare#user-api-token-authentication-recommended",
|
|
api_key:
|
|
"https://docs.prowler.com/user-guide/providers/cloudflare/getting-started-cloudflare#api-key-and-email-authentication-legacy",
|
|
},
|
|
};
|
|
|
|
const PROVIDER_HELP_FALLBACK_URL = "https://goto.prowler.com/provider-help";
|
|
|
|
const getOwnRecordValue = <T>(
|
|
record: Readonly<Record<string, T>>,
|
|
key: string,
|
|
): T | undefined => (Object.hasOwn(record, key) ? record[key] : undefined);
|
|
|
|
const resolveDocsLink = (
|
|
provider: string,
|
|
step?: ProviderWizardStep,
|
|
credentialsMethod?: string | null,
|
|
) => {
|
|
const shortlink = getOwnRecordValue(PROVIDER_DOCS_SHORTLINK, provider);
|
|
|
|
if (step === PROVIDER_WIZARD_STEP.CREDENTIALS) {
|
|
if (credentialsMethod) {
|
|
const methodDocs = getOwnRecordValue(
|
|
PROVIDER_CREDENTIALS_METHOD_DOCS_URL,
|
|
provider,
|
|
);
|
|
const methodUrl = methodDocs
|
|
? getOwnRecordValue(methodDocs, credentialsMethod)
|
|
: undefined;
|
|
if (methodUrl) return methodUrl;
|
|
}
|
|
|
|
const stepUrl = getOwnRecordValue(
|
|
PROVIDER_CREDENTIALS_STEP_DOCS_URL,
|
|
provider,
|
|
);
|
|
if (stepUrl) return stepUrl;
|
|
}
|
|
|
|
return shortlink;
|
|
};
|
|
|
|
const PROVIDER_HELP_TEXT: Record<string, string> = {
|
|
aws: "Need help connecting your AWS account?",
|
|
azure: "Need help connecting your Azure subscription?",
|
|
m365: "Need help connecting your Microsoft 365 account?",
|
|
gcp: "Need help connecting your GCP project?",
|
|
kubernetes: "Need help connecting your Kubernetes cluster?",
|
|
github: "Need help connecting your GitHub account?",
|
|
iac: "Need help scanning your Infrastructure as Code repository?",
|
|
image: "Need help scanning your container registry?",
|
|
oraclecloud: "Need help connecting your Oracle Cloud account?",
|
|
mongodbatlas: "Need help connecting your MongoDB Atlas organization?",
|
|
alibabacloud: "Need help connecting your Alibaba Cloud account?",
|
|
cloudflare: "Need help connecting your Cloudflare account?",
|
|
openstack: "Need help connecting your OpenStack cloud?",
|
|
googleworkspace: "Need help connecting your Google Workspace account?",
|
|
vercel: "Need help connecting your Vercel team?",
|
|
okta: "Need help connecting your Okta organization?",
|
|
};
|
|
|
|
export const getProviderHelpText = (
|
|
provider: string,
|
|
step?: ProviderWizardStep,
|
|
credentialsMethod?: string | null,
|
|
) => {
|
|
const link = resolveDocsLink(provider, step, credentialsMethod);
|
|
|
|
if (!link) {
|
|
// Unknown provider: hand off to the generic help shortlink instead of
|
|
// deep-linking into a page that may not exist.
|
|
return {
|
|
text: "How to setup a provider?",
|
|
link: PROVIDER_HELP_FALLBACK_URL,
|
|
};
|
|
}
|
|
|
|
return {
|
|
text:
|
|
getOwnRecordValue(PROVIDER_HELP_TEXT, provider) ??
|
|
"Need help connecting your provider?",
|
|
link,
|
|
};
|
|
};
|
|
|
|
export const getAWSCredentialsTemplateLinks = (
|
|
externalId: string,
|
|
bucketName?: string,
|
|
integrationType?: IntegrationType,
|
|
bucketAccountId?: string,
|
|
): {
|
|
cloudformation: string;
|
|
terraform: string;
|
|
cloudformationQuickLink: string;
|
|
} => {
|
|
let links = {};
|
|
|
|
if (integrationType === undefined || integrationType === "aws_security_hub") {
|
|
links = {
|
|
cloudformation:
|
|
"https://github.com/prowler-cloud/prowler/blob/master/permissions/templates/cloudformation/prowler-scan-role.yml",
|
|
terraform:
|
|
"https://github.com/prowler-cloud/prowler/tree/master/permissions/templates/terraform",
|
|
};
|
|
}
|
|
|
|
if (integrationType === "amazon_s3") {
|
|
links = {
|
|
cloudformation:
|
|
"https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app-s3-integration/",
|
|
terraform:
|
|
"https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app-s3-integration/#terraform",
|
|
};
|
|
}
|
|
|
|
// The template requires S3IntegrationBucketAccountId (owner account of the
|
|
// bucket) whenever EnableS3Integration is true. Only enable S3 when both the
|
|
// bucket name and its account id are known, otherwise an incomplete link
|
|
// would fail stack validation on the quick-create flow (reachable from the
|
|
// edit-credentials flow, where the account id can resolve to an empty value).
|
|
const parameters: Record<string, string> = {
|
|
param_ExternalId: externalId,
|
|
};
|
|
|
|
if (bucketName && bucketAccountId) {
|
|
parameters.param_EnableS3Integration = "true";
|
|
parameters.param_S3IntegrationBucketName = bucketName;
|
|
parameters.param_S3IntegrationBucketAccountId = bucketAccountId;
|
|
}
|
|
|
|
return {
|
|
...(links as {
|
|
cloudformation: string;
|
|
terraform: string;
|
|
}),
|
|
cloudformationQuickLink: buildCloudFormationQuickCreateLink(parameters),
|
|
};
|
|
};
|
|
|
|
// Builds the CloudFormation quick-create link that onboards an entire AWS
|
|
// Organization in a single stack: it creates the ProwlerScan role in the
|
|
// account launching the stack (DeployLocalRole) and a service-managed StackSet
|
|
// that rolls the role out to the member accounts under the given OU/root
|
|
// (DeployStackSet). By default the stack is launched from the management
|
|
// account; set deployFromDelegatedAdmin when launching from a delegated
|
|
// administrator account instead, where the local role lands in that account.
|
|
export const getAWSOrgDeploymentQuickLink = ({
|
|
externalId,
|
|
organizationalUnitId,
|
|
deployFromDelegatedAdmin = false,
|
|
}: AWSOrgDeploymentQuickLinkParams): string => {
|
|
const parameters: Record<string, string> = {
|
|
param_ExternalId: externalId,
|
|
param_EnableOrganizations: "true",
|
|
param_DeployLocalRole: "true",
|
|
param_DeployStackSet: "true",
|
|
param_AWSOrganizationalUnitId: organizationalUnitId,
|
|
};
|
|
|
|
if (deployFromDelegatedAdmin) {
|
|
parameters.param_DeployFromDelegatedAdmin = "true";
|
|
}
|
|
|
|
return buildCloudFormationQuickCreateLink(parameters);
|
|
};
|