mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
feat(stepfunctions): add check for secrets in state machine definition (#10570)
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
co-authored by
Andoni A.
parent
cccb3a4b94
commit
b898f257f1
@@ -21,6 +21,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222)
|
||||
- `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234)
|
||||
- `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189)
|
||||
- `stepfunctions_statemachine_no_secrets_in_definition` check for hardcoded secrets in AWS Step Functions state machine definitions [(#10570)](https://github.com/prowler-cloud/prowler/pull/10570)
|
||||
- CCC improvements with the latest checks and new mappings [(#10625)](https://github.com/prowler-cloud/prowler/pull/10625)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "stepfunctions_statemachine_no_secrets_in_definition",
|
||||
"CheckTitle": "Step Functions state machine has no sensitive credentials in its definition",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices",
|
||||
"TTPs/Credential Access",
|
||||
"Effects/Data Exposure",
|
||||
"Sensitive Data Identifications/Security"
|
||||
],
|
||||
"ServiceName": "stepfunctions",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AwsStepFunctionStateMachine",
|
||||
"ResourceGroup": "serverless",
|
||||
"Description": "**AWS Step Functions state machines** are inspected for **hardcoded secrets** (keys, tokens, passwords) embedded directly in the state machine **definition** (Amazon States Language JSON).\n\nSuch values indicate sensitive data is stored directly in task parameters instead of being sourced securely.",
|
||||
"Risk": "Plaintext secrets in state machine definitions reduce confidentiality: values can be viewed in the AWS Console, CLI, and may leak into execution logs or public outputs. Compromised credentials enable unauthorized AWS actions, lateral movement, and data exfiltration.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html",
|
||||
"https://docs.aws.amazon.com/step-functions/latest/dg/security-best-practices.html",
|
||||
"https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_how-services-use-secrets_step-functions.html",
|
||||
"https://docs.aws.amazon.com/systems-manager/latest/userguide/integration-ps-secretsmanager.html"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::StepFunctions::StateMachine\n Properties:\n StateMachineName: <example_resource_name>\n RoleArn: <example_resource_arn>\n DefinitionString: |\n {\n \"Comment\": \"Example state machine\",\n \"StartAt\": \"MyTask\",\n \"States\": {\n \"MyTask\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::aws-sdk:secretsmanager:getSecretValue\",\n \"Parameters\": {\n \"SecretId\": \"<example_secret_name>\"\n },\n \"End\": true\n }\n }\n }\n```",
|
||||
"Other": "1. In AWS Console, go to Step Functions and open your state machine\n2. Click Edit\n3. Remove any hardcoded secrets from the definition\n4. Use AWS Secrets Manager or Parameter Store to retrieve secrets at runtime\n5. Grant the state machine IAM role permission to access the secret\n6. Save the updated definition",
|
||||
"Terraform": "```hcl\nresource \"aws_sfn_state_machine\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n role_arn = \"<example_resource_arn>\"\n\n definition = jsonencode({\n Comment = \"Example state machine\"\n StartAt = \"MyTask\"\n States = {\n MyTask = {\n Type = \"Task\"\n Resource = \"arn:aws:states:::aws-sdk:secretsmanager:getSecretValue\"\n Parameters = {\n SecretId = \"<example_secret_name>\" # Reference secret by name, never hardcode value\n }\n End = true\n }\n }\n })\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Store secrets outside the state machine definition and retrieve them securely at runtime using **AWS Secrets Manager** or **AWS Systems Manager Parameter Store**.\n- Use the `aws-sdk:secretsmanager:getSecretValue` integration to fetch secrets dynamically\n- Enforce **least privilege** on the state machine IAM role\n- Rotate secrets regularly and never embed them in the definition",
|
||||
"Url": "https://hub.prowler.com/check/stepfunctions_statemachine_no_secrets_in_definition"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"secrets"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.lib.utils.utils import detect_secrets_scan
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_client import (
|
||||
stepfunctions_client,
|
||||
)
|
||||
|
||||
|
||||
class stepfunctions_statemachine_no_secrets_in_definition(Check):
|
||||
"""Check that AWS Step Functions state machine definitions contain no hardcoded secrets."""
|
||||
|
||||
def execute(self) -> list[Check_Report_AWS]:
|
||||
findings = []
|
||||
secrets_ignore_patterns = stepfunctions_client.audit_config.get(
|
||||
"secrets_ignore_patterns", []
|
||||
)
|
||||
for state_machine in stepfunctions_client.state_machines.values():
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=state_machine)
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"No secrets found in Step Functions state machine {state_machine.name} definition."
|
||||
|
||||
if state_machine.definition:
|
||||
detect_secrets_output = detect_secrets_scan(
|
||||
data=state_machine.definition,
|
||||
excluded_secrets=secrets_ignore_patterns,
|
||||
detect_secrets_plugins=stepfunctions_client.audit_config.get(
|
||||
"detect_secrets_plugins",
|
||||
),
|
||||
)
|
||||
|
||||
if detect_secrets_output:
|
||||
secrets_string = ", ".join(
|
||||
[
|
||||
f"{secret['type']} on line {secret['line_number']}"
|
||||
for secret in detect_secrets_output
|
||||
]
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Potential {'secrets' if len(detect_secrets_output) > 1 else 'secret'} "
|
||||
f"found in Step Functions state machine {state_machine.name} definition "
|
||||
f"-> {secrets_string}."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
|
||||
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1
|
||||
|
||||
|
||||
class Test_stepfunctions_statemachine_no_secrets_in_definition:
|
||||
def test_no_statemachines(self):
|
||||
stepfunctions_client = mock.MagicMock()
|
||||
stepfunctions_client.state_machines = {}
|
||||
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
|
||||
stepfunctions_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
|
||||
stepfunctions_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
|
||||
stepfunctions_statemachine_no_secrets_in_definition,
|
||||
)
|
||||
|
||||
check = stepfunctions_statemachine_no_secrets_in_definition()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
def test_statemachine_with_no_definition(self):
|
||||
stepfunctions_client = mock.MagicMock()
|
||||
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
|
||||
StateMachine,
|
||||
StateMachineStatus,
|
||||
StateMachineType,
|
||||
)
|
||||
|
||||
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
|
||||
stepfunctions_client.state_machines = {
|
||||
statemachine_arn: StateMachine(
|
||||
id="TestStateMachine",
|
||||
arn=statemachine_arn,
|
||||
name="TestStateMachine",
|
||||
status=StateMachineStatus.ACTIVE,
|
||||
definition=None,
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
type=StateMachineType.STANDARD,
|
||||
creation_date=datetime.now(),
|
||||
)
|
||||
}
|
||||
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
|
||||
stepfunctions_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
|
||||
stepfunctions_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
|
||||
stepfunctions_statemachine_no_secrets_in_definition,
|
||||
)
|
||||
|
||||
check = stepfunctions_statemachine_no_secrets_in_definition()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "No secrets found in Step Functions state machine TestStateMachine definition."
|
||||
)
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_id == "TestStateMachine"
|
||||
assert result[0].resource_arn == statemachine_arn
|
||||
|
||||
def test_statemachine_with_no_secrets_in_definition(self):
|
||||
stepfunctions_client = mock.MagicMock()
|
||||
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
|
||||
StateMachine,
|
||||
StateMachineStatus,
|
||||
StateMachineType,
|
||||
)
|
||||
|
||||
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
|
||||
stepfunctions_client.state_machines = {
|
||||
statemachine_arn: StateMachine(
|
||||
id="TestStateMachine",
|
||||
arn=statemachine_arn,
|
||||
name="TestStateMachine",
|
||||
status=StateMachineStatus.ACTIVE,
|
||||
definition='{"Comment": "A simple example", "StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Pass", "End": true}}}',
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
type=StateMachineType.STANDARD,
|
||||
creation_date=datetime.now(),
|
||||
)
|
||||
}
|
||||
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
|
||||
stepfunctions_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
|
||||
stepfunctions_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
|
||||
stepfunctions_statemachine_no_secrets_in_definition,
|
||||
)
|
||||
|
||||
check = stepfunctions_statemachine_no_secrets_in_definition()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "No secrets found in Step Functions state machine TestStateMachine definition."
|
||||
)
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_id == "TestStateMachine"
|
||||
assert result[0].resource_arn == statemachine_arn
|
||||
|
||||
def test_statemachine_with_secrets_in_definition(self):
|
||||
stepfunctions_client = mock.MagicMock()
|
||||
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
|
||||
StateMachine,
|
||||
StateMachineStatus,
|
||||
StateMachineType,
|
||||
)
|
||||
|
||||
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
|
||||
stepfunctions_client.state_machines = {
|
||||
statemachine_arn: StateMachine(
|
||||
id="TestStateMachine",
|
||||
arn=statemachine_arn,
|
||||
name="TestStateMachine",
|
||||
status=StateMachineStatus.ACTIVE,
|
||||
definition='{"Comment": "Example with secret", "StartAt": "MyTask", "States": {"MyTask": {"Type": "Task", "Parameters": {"api_key": "AKIAIOSFODNN7EXAMPLE"}, "End": true}}}',
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
type=StateMachineType.STANDARD,
|
||||
creation_date=datetime.now(),
|
||||
)
|
||||
}
|
||||
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
|
||||
stepfunctions_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
|
||||
stepfunctions_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
|
||||
stepfunctions_statemachine_no_secrets_in_definition,
|
||||
)
|
||||
|
||||
check = stepfunctions_statemachine_no_secrets_in_definition()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "TestStateMachine" in result[0].status_extended
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_id == "TestStateMachine"
|
||||
assert result[0].resource_arn == statemachine_arn
|
||||
Reference in New Issue
Block a user