mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-04 06:28:14 +00:00
Compare commits
5 Commits
poc-gha-ia
...
wanr-sensi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16b96cf7a2 | ||
|
|
246a29bd39 | ||
|
|
d59abbba6e | ||
|
|
1940cdc674 | ||
|
|
78db8b6ce5 |
@@ -750,6 +750,35 @@ def init_parser(self):
|
||||
# More arguments for the provider.
|
||||
```
|
||||
|
||||
##### Sensitive CLI Arguments
|
||||
|
||||
CLI flags that accept secrets (tokens, passwords, API keys) require special handling to protect credentials from leaking in HTML output and process listings:
|
||||
|
||||
1. **Use `nargs="?"` with `default=None`** so the flag works both with and without an inline value. This allows the provider to fall back to an environment variable when no value is passed.
|
||||
2. **Add a `SENSITIVE_ARGUMENTS` frozenset** at the top of the `arguments.py` file listing every flag that accepts secret values:
|
||||
|
||||
```python
|
||||
SENSITIVE_ARGUMENTS = frozenset({"--your-provider-password", "--your-provider-token"})
|
||||
```
|
||||
|
||||
Prowler automatically discovers these frozensets and uses them to redact values in HTML output and warn users who pass secrets directly on the command line.
|
||||
|
||||
3. **Document the environment variable** in the `help` text so users know the recommended alternative:
|
||||
|
||||
```python
|
||||
<provider_name>_parser.add_argument(
|
||||
"--your-provider-password",
|
||||
nargs="?",
|
||||
default=None,
|
||||
metavar="PASSWORD",
|
||||
help="Password for authentication. We recommend using the YOUR_PROVIDER_PASSWORD environment variable instead.",
|
||||
)
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Do not add new arguments that require passing secrets as CLI values without an environment variable fallback. Prowler CLI warns users when sensitive flags receive explicit values on the command line.
|
||||
</Warning>
|
||||
|
||||
#### Step 5: Implement Mutelist
|
||||
|
||||
**Explanation:**
|
||||
|
||||
@@ -66,22 +66,38 @@ prowler <provider> --categories internet-exposed
|
||||
|
||||
### Shodan
|
||||
|
||||
Prowler allows you check if any public IPs in your Cloud environments are exposed in Shodan with the `-N`/`--shodan <shodan_api_key>` option:
|
||||
Prowler can check whether any public IPs in cloud environments are exposed in Shodan using the `-N`/`--shodan` option.
|
||||
|
||||
For example, you can check if any of your AWS Elastic Compute Cloud (EC2) instances has an elastic IP exposed in Shodan:
|
||||
#### Using the Environment Variable (Recommended)
|
||||
|
||||
Set the `SHODAN_API_KEY` environment variable to avoid exposing the API key in process listings and shell history:
|
||||
|
||||
```console
|
||||
prowler aws -N/--shodan <shodan_api_key> -c ec2_elastic_ip_shodan
|
||||
export SHODAN_API_KEY=<shodan_api_key>
|
||||
```
|
||||
|
||||
Also, you can check if any of your Azure Subscription has an public IP exposed in Shodan:
|
||||
Then run Prowler with the `--shodan` flag (no value needed):
|
||||
|
||||
```console
|
||||
prowler azure -N/--shodan <shodan_api_key> -c network_public_ip_shodan
|
||||
prowler aws --shodan -c ec2_elastic_ip_shodan
|
||||
```
|
||||
|
||||
And finally, you can check if any of your GCP projects has an public IP address exposed in Shodan:
|
||||
|
||||
```console
|
||||
prowler gcp -N/--shodan <shodan_api_key> -c compute_public_address_shodan
|
||||
prowler azure --shodan -c network_public_ip_shodan
|
||||
```
|
||||
|
||||
```console
|
||||
prowler gcp --shodan -c compute_public_address_shodan
|
||||
```
|
||||
|
||||
#### Using the CLI Flag
|
||||
|
||||
Alternatively, pass the API key directly on the command line:
|
||||
|
||||
```console
|
||||
prowler aws --shodan <shodan_api_key> -c ec2_elastic_ip_shodan
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Passing secret values directly on the command line exposes them in process listings and shell history. Prowler CLI displays a warning when this pattern is detected. Use the `SHODAN_API_KEY` environment variable instead.
|
||||
</Warning>
|
||||
|
||||
@@ -24,6 +24,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
- Added `internet-exposed` category to 13 AWS checks (CloudFront, CodeArtifact, EC2, EFS, RDS, SageMaker, Shield, VPC) [(#10502)](https://github.com/prowler-cloud/prowler/pull/10502)
|
||||
- Minimum Python version from 3.9 to 3.10 and updated classifiers to reflect supported versions (3.10, 3.11, 3.12) [(#10464)](https://github.com/prowler-cloud/prowler/pull/10464)
|
||||
- Sensitive CLI flags now warn when values are passed directly, recommending environment variables instead [(#10532)](https://github.com/prowler-cloud/prowler/pull/10532)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from prowler.config.config import (
|
||||
default_output_directory,
|
||||
)
|
||||
from prowler.lib.check.models import Severity
|
||||
from prowler.lib.cli.redact import warn_sensitive_argument_values
|
||||
from prowler.lib.outputs.common import Status
|
||||
from prowler.providers.common.arguments import (
|
||||
init_providers_parser,
|
||||
@@ -19,8 +20,6 @@ from prowler.providers.common.arguments import (
|
||||
validate_provider_arguments,
|
||||
)
|
||||
|
||||
SENSITIVE_ARGUMENTS = frozenset({"--shodan"})
|
||||
|
||||
|
||||
class ProwlerArgumentParser:
|
||||
# Set the default parser
|
||||
@@ -126,6 +125,10 @@ Detailed documentation at https://docs.prowler.com
|
||||
elif sys.argv[1] == "oci":
|
||||
sys.argv[1] = "oraclecloud"
|
||||
|
||||
# Warn about sensitive flags passed with explicit values
|
||||
# Snapshot argv before parse_args() which may exit on errors
|
||||
warn_sensitive_argument_values(list(sys.argv[1:]))
|
||||
|
||||
# Parse arguments
|
||||
args = self.parser.parse_args()
|
||||
|
||||
@@ -434,7 +437,7 @@ Detailed documentation at https://docs.prowler.com
|
||||
nargs="?",
|
||||
default=None,
|
||||
metavar="SHODAN_API_KEY",
|
||||
help="Check if any public IPs in your Cloud environments are exposed in Shodan.",
|
||||
help="Check if any public IPs in your Cloud environments are exposed in Shodan. We recommend to use the SHODAN_API_KEY environment variable to provide the API key.",
|
||||
)
|
||||
third_party_subparser.add_argument(
|
||||
"--slack",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from functools import lru_cache
|
||||
from importlib import import_module
|
||||
|
||||
from colorama import Fore, Style
|
||||
|
||||
from prowler.lib.cli.sensitive import SENSITIVE_ARGUMENTS as COMMON_SENSITIVE_ARGUMENTS
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.common.provider import Provider, providers_path
|
||||
|
||||
@@ -13,11 +16,7 @@ def get_sensitive_arguments() -> frozenset:
|
||||
sensitive: set[str] = set()
|
||||
|
||||
# Common parser sensitive arguments (e.g., --shodan)
|
||||
try:
|
||||
parser_module = import_module("prowler.lib.cli.parser")
|
||||
sensitive.update(getattr(parser_module, "SENSITIVE_ARGUMENTS", frozenset()))
|
||||
except Exception as error:
|
||||
logger.debug(f"Could not load SENSITIVE_ARGUMENTS from parser: {error}")
|
||||
sensitive.update(COMMON_SENSITIVE_ARGUMENTS)
|
||||
|
||||
# Provider-specific sensitive arguments
|
||||
for provider in Provider.get_available_providers():
|
||||
@@ -66,3 +65,49 @@ def redact_argv(argv: list[str]) -> str:
|
||||
result.append(arg)
|
||||
|
||||
return " ".join(result)
|
||||
|
||||
|
||||
def warn_sensitive_argument_values(argv: list[str]) -> None:
|
||||
"""Log a warning for each sensitive CLI flag that was passed with an explicit value.
|
||||
|
||||
Scans the raw argv list (not parsed args) to detect when users pass
|
||||
secret values directly on the command line instead of using environment
|
||||
variables. Handles both ``--flag value`` and ``--flag=value`` syntax.
|
||||
|
||||
Args:
|
||||
argv: The argument list to check (typically ``sys.argv[1:]``).
|
||||
"""
|
||||
sensitive = get_sensitive_arguments()
|
||||
if not sensitive:
|
||||
return
|
||||
|
||||
use_color = "--no-color" not in argv
|
||||
flags_with_values: list[str] = []
|
||||
|
||||
for i, arg in enumerate(argv):
|
||||
# --flag=value syntax
|
||||
if "=" in arg:
|
||||
flag = arg.split("=", 1)[0]
|
||||
if flag in sensitive:
|
||||
flags_with_values.append(flag)
|
||||
continue
|
||||
|
||||
# --flag value syntax
|
||||
if arg in sensitive:
|
||||
if i + 1 < len(argv) and not argv[i + 1].startswith("-"):
|
||||
flags_with_values.append(arg)
|
||||
|
||||
for flag in flags_with_values:
|
||||
if use_color:
|
||||
logger.warning(
|
||||
f"{Fore.YELLOW}{Style.BRIGHT}WARNING:{Style.RESET_ALL}{Fore.YELLOW} "
|
||||
f"Passing a value directly to {flag} is not recommended. "
|
||||
f"Use the corresponding environment variable instead to avoid "
|
||||
f"exposing secrets in process listings and shell history.{Style.RESET_ALL}"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Passing a value directly to {flag} is not recommended. "
|
||||
f"Use the corresponding environment variable instead to avoid "
|
||||
f"exposing secrets in process listings and shell history."
|
||||
)
|
||||
|
||||
8
prowler/lib/cli/sensitive.py
Normal file
8
prowler/lib/cli/sensitive.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Common parser sensitive arguments.
|
||||
|
||||
This module is kept dependency-free (no prowler-internal imports) so that
|
||||
``prowler.lib.cli.redact`` and any provider argument module can import it
|
||||
without circular-import risk.
|
||||
"""
|
||||
|
||||
SENSITIVE_ARGUMENTS = frozenset({"--shodan"})
|
||||
@@ -13,7 +13,11 @@ def init_parser(self):
|
||||
"--nhn-username", nargs="?", default=None, help="NHN API Username"
|
||||
)
|
||||
nhn_auth_subparser.add_argument(
|
||||
"--nhn-password", nargs="?", default=None, help="NHN API Password"
|
||||
"--nhn-password",
|
||||
nargs="?",
|
||||
default=None,
|
||||
metavar="NHN_PASSWORD",
|
||||
help="NHN API Password",
|
||||
)
|
||||
nhn_auth_subparser.add_argument(
|
||||
"--nhn-tenant-id", nargs="?", default=None, help="NHN Tenant ID"
|
||||
|
||||
@@ -46,6 +46,7 @@ def init_parser(self):
|
||||
"--os-password",
|
||||
nargs="?",
|
||||
default=None,
|
||||
metavar="OS_PASSWORD",
|
||||
help="OpenStack password for authentication. Can also be set via OS_PASSWORD environment variable",
|
||||
)
|
||||
openstack_explicit_subparser.add_argument(
|
||||
|
||||
@@ -45,6 +45,34 @@ prowler/providers/{provider}/
|
||||
└── {check_name}.metadata.json
|
||||
```
|
||||
|
||||
## Sensitive CLI Arguments
|
||||
|
||||
Flags that accept secrets (tokens, passwords, API keys) MUST follow these rules:
|
||||
|
||||
1. **Use `nargs="?"` with `default=None`** — the flag accepts an optional value for backward compatibility; the recommended path is environment variables.
|
||||
2. **Set `metavar` to the environment variable name** users should use (e.g., `metavar="GITHUB_PERSONAL_ACCESS_TOKEN"`).
|
||||
3. **Add the flag to the `SENSITIVE_ARGUMENTS` frozenset** at the top of the provider's `arguments.py`. This set is used to redact values in HTML output and warn users who pass secrets directly.
|
||||
4. **Do not add new arguments that require passing secrets as CLI values** — secrets should come from environment variables. The flag accepts a value for backward compatibility, but CLI warns users to prefer env vars.
|
||||
|
||||
### Pattern
|
||||
|
||||
```python
|
||||
# prowler/providers/{provider}/lib/arguments/arguments.py
|
||||
|
||||
SENSITIVE_ARGUMENTS = frozenset({"--my-api-key", "--my-password"})
|
||||
|
||||
|
||||
def init_parser(self):
|
||||
auth_subparser = parser.add_argument_group("Authentication Modes")
|
||||
auth_subparser.add_argument(
|
||||
"--my-api-key",
|
||||
nargs="?",
|
||||
default=None,
|
||||
metavar="MY_API_KEY",
|
||||
help="API key for authentication. Use MY_API_KEY env var instead of passing directly.",
|
||||
)
|
||||
```
|
||||
|
||||
## Provider Class Template
|
||||
|
||||
```python
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import logging
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from prowler.lib.cli.redact import REDACTED_VALUE, get_sensitive_arguments, redact_argv
|
||||
from prowler.lib.cli.redact import (
|
||||
REDACTED_VALUE,
|
||||
get_sensitive_arguments,
|
||||
redact_argv,
|
||||
warn_sensitive_argument_values,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -87,6 +93,62 @@ class TestRedactArgv:
|
||||
assert redact_argv(argv) == "aws --region=us-east-1"
|
||||
|
||||
|
||||
class TestWarnSensitiveArgumentValues:
|
||||
def test_no_warning_without_sensitive_flags(self, caplog, mock_sensitive_args):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
warn_sensitive_argument_values(["aws", "--region", "eu-west-1"])
|
||||
assert caplog.text == ""
|
||||
|
||||
def test_no_warning_flag_without_value(self, caplog, mock_sensitive_args):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
warn_sensitive_argument_values(["github", "--personal-access-token"])
|
||||
assert caplog.text == ""
|
||||
|
||||
def test_no_warning_flag_followed_by_another_flag(
|
||||
self, caplog, mock_sensitive_args
|
||||
):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
warn_sensitive_argument_values(
|
||||
["github", "--personal-access-token", "--region", "eu-west-1"]
|
||||
)
|
||||
assert caplog.text == ""
|
||||
|
||||
def test_warning_flag_with_value(self, caplog, mock_sensitive_args):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
warn_sensitive_argument_values(
|
||||
["github", "--personal-access-token", "ghp_secret"]
|
||||
)
|
||||
assert "--personal-access-token" in caplog.text
|
||||
assert "not recommended" in caplog.text
|
||||
|
||||
def test_warning_flag_with_equals_syntax(self, caplog, mock_sensitive_args):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
warn_sensitive_argument_values(["aws", "--shodan=key123"])
|
||||
assert "--shodan" in caplog.text
|
||||
assert "not recommended" in caplog.text
|
||||
|
||||
def test_warning_multiple_flags(self, caplog, mock_sensitive_args):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
warn_sensitive_argument_values(
|
||||
[
|
||||
"github",
|
||||
"--personal-access-token",
|
||||
"ghp_secret",
|
||||
"--shodan",
|
||||
"key",
|
||||
]
|
||||
)
|
||||
assert "--personal-access-token" in caplog.text
|
||||
assert "--shodan" in caplog.text
|
||||
|
||||
def test_no_color_output(self, caplog, mock_sensitive_args):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
warn_sensitive_argument_values(["--no-color", "aws", "--shodan", "key123"])
|
||||
assert "not recommended" in caplog.text
|
||||
# Should not contain ANSI escape codes
|
||||
assert "\033[" not in caplog.text
|
||||
|
||||
|
||||
class TestGetSensitiveArguments:
|
||||
def test_discovers_known_sensitive_arguments(self):
|
||||
"""Integration test: verify the discovery mechanism finds flags from provider modules."""
|
||||
|
||||
Reference in New Issue
Block a user