diff --git a/.env b/.env index 99ec6b55d2..5560a0504e 100644 --- a/.env +++ b/.env @@ -202,3 +202,8 @@ LANGCHAIN_PROJECT="" RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true}]' # Example with multiple sources (no trailing comma after last item): # RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true},{"id":"prowler-blog","name":"Prowler Blog","type":"blog","url":"https://prowler.com/blog/rss","enabled":false}]' + +# Comma-separated IPs and CIDRs the SSRF guard must not block, for scanning +# Kubernetes, IaC or OpenStack targets that live on a private network. +# Read by the worker, which runs the scan, not by the API. +PROWLER_ALLOWED_PRIVATE_NETWORKS="" diff --git a/docs/user-guide/providers/kubernetes/misc.mdx b/docs/user-guide/providers/kubernetes/misc.mdx index 0df4556f24..fa0596bc1a 100644 --- a/docs/user-guide/providers/kubernetes/misc.mdx +++ b/docs/user-guide/providers/kubernetes/misc.mdx @@ -55,6 +55,18 @@ prowler kubernetes ... The value is a comma-separated list of IPs and CIDRs. A resolved address inside an allowlisted range is permitted; every other non-public address stays blocked, so link-local (`169.254.169.254`), loopback, and the rest of the internal network remain protected. A kubeconfig declaring several clusters is rejected when any one of them resolves outside the allowlist. Malformed entries are rejected, and a non-empty allowlist is logged as a relaxed security control. When unset, only public addresses are reachable. -The variable is read by the process that runs the scan. In Prowler App that is the worker, not the API, so setting it only on the API container has no effect. The same variable applies to the IaC and OpenStack providers. +The variable is read by the process that runs the scan: + +- **CLI**: export it in the shell running `prowler`. +- **Docker Compose**: set it in the root `.env`; it reaches the worker through the shared environment file. +- **Helm**: add it under `api.djangoConfig`, which is rendered into the API ConfigMap that the worker inherits through `envFrom`. + +```yaml +api: + djangoConfig: + PROWLER_ALLOWED_PRIVATE_NETWORKS: "10.20.0.0/16" +``` + +In Prowler App the scan runs in the worker, not the API, so setting the variable only on the API container has no effect. The same variable applies to the IaC and OpenStack providers. The Image provider has its own, `PROWLER_IMAGE_PROVIDER_ALLOWED_PRIVATE_NETWORKS`. The check resolves the cluster hostname locally, before the Kubernetes client connects. If egress is only possible through `HTTPS_PROXY` and the hostname cannot be resolved locally, declare the cluster's address range in `PROWLER_ALLOWED_PRIVATE_NETWORKS` or make the name resolvable to the scanning process. diff --git a/prowler/lib/network/ssrf.py b/prowler/lib/network/ssrf.py index 30f5e9b861..514c062995 100644 --- a/prowler/lib/network/ssrf.py +++ b/prowler/lib/network/ssrf.py @@ -126,7 +126,10 @@ def validate_outbound_host(host: str) -> None: for address in addresses: if _ip_is_non_public(address) and not _ip_is_allowlisted(address, networks): raise OutboundURLNotAllowedError( - f"Host {host!r} resolves to non-public address {address} and cannot be reached" + f"Host {host!r} resolves to non-public address {address} and cannot be " + f"reached. To scan a target on a private network, list the trusted " + f"ranges in the {ALLOWED_PRIVATE_NETWORKS_ENV} environment variable of " + f"the process running the scan" )