feat(awslambda): enrich Function model with inventory fields and add 3 security checks (#10381)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
Sandiyo Christan
2026-03-26 10:33:39 +01:00
committed by GitHub
co-authored by Daniel Barranquero
parent 2cf45c72b6
commit 834d1bca49
14 changed files with 818 additions and 3 deletions
@@ -0,0 +1,197 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_EU_WEST_1,
set_mocked_aws_provider,
)
ROLE_POLICY = dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
)
class Test_awslambda_function_env_vars_not_encrypted_with_cmk:
def test_no_functions(self):
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_client",
new=Lambda(aws_provider),
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk import (
awslambda_function_env_vars_not_encrypted_with_cmk,
)
check = awslambda_function_env_vars_not_encrypted_with_cmk()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_function_no_env_vars(self):
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = iam_client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=ROLE_POLICY,
)["Role"]["Arn"]
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-fn-no-env"
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_client",
new=Lambda(aws_provider),
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk import (
awslambda_function_env_vars_not_encrypted_with_cmk,
)
check = awslambda_function_env_vars_not_encrypted_with_cmk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "no environment variables" in result[0].status_extended
assert result[0].resource_id == function_name
assert result[0].resource_arn == function_arn
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_function_env_vars_no_kms(self):
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = iam_client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=ROLE_POLICY,
)["Role"]["Arn"]
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-fn-env-no-kms"
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
Environment={"Variables": {"DB_HOST": "localhost"}},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_client",
new=Lambda(aws_provider),
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk import (
awslambda_function_env_vars_not_encrypted_with_cmk,
)
check = awslambda_function_env_vars_not_encrypted_with_cmk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "customer-managed KMS key" in result[0].status_extended
assert result[0].resource_id == function_name
assert result[0].resource_arn == function_arn
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_function_env_vars_with_cmk(self):
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = iam_client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=ROLE_POLICY,
)["Role"]["Arn"]
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-fn-env-with-kms"
key_arn = (
f"arn:aws:kms:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:key/test-key-id"
)
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
Environment={"Variables": {"DB_HOST": "localhost"}},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
lambda_service = Lambda(aws_provider)
# moto does not return KMSKeyArn in list_functions; inject it to test PASS branch.
lambda_service.functions[function_arn].kms_key_arn = key_arn
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_client",
new=lambda_service,
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_env_vars_not_encrypted_with_cmk.awslambda_function_env_vars_not_encrypted_with_cmk import (
awslambda_function_env_vars_not_encrypted_with_cmk,
)
check = awslambda_function_env_vars_not_encrypted_with_cmk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert key_arn in result[0].status_extended
assert result[0].resource_id == function_name
assert result[0].resource_arn == function_arn
assert result[0].region == AWS_REGION_EU_WEST_1
@@ -0,0 +1,149 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.awslambda.awslambda_service import DeadLetterConfig
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
ROLE_POLICY = dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
)
class Test_awslambda_function_no_dead_letter_queue:
def test_no_functions(self):
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_no_dead_letter_queue.awslambda_function_no_dead_letter_queue.awslambda_client",
new=Lambda(aws_provider),
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_no_dead_letter_queue.awslambda_function_no_dead_letter_queue import (
awslambda_function_no_dead_letter_queue,
)
check = awslambda_function_no_dead_letter_queue()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_function_without_dlq(self):
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = iam_client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=ROLE_POLICY,
)["Role"]["Arn"]
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-function-no-dlq"
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_no_dead_letter_queue.awslambda_function_no_dead_letter_queue.awslambda_client",
new=Lambda(aws_provider),
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_no_dead_letter_queue.awslambda_function_no_dead_letter_queue import (
awslambda_function_no_dead_letter_queue,
)
check = awslambda_function_no_dead_letter_queue()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert function_name in result[0].status_extended
assert "Dead Letter Queue" in result[0].status_extended
assert result[0].resource_id == function_name
assert result[0].resource_arn == function_arn
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_function_with_sqs_dlq(self):
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = iam_client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=ROLE_POLICY,
)["Role"]["Arn"]
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-function-with-dlq"
queue_arn = f"arn:aws:sqs:{AWS_REGION_EU_WEST_1}:123456789012:test-dlq"
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
lambda_service = Lambda(aws_provider)
# moto does not return DeadLetterConfig in list_functions;
# set it directly to test the PASS branch of the check logic.
lambda_service.functions[function_arn].dead_letter_config = DeadLetterConfig(
target_arn=queue_arn
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_no_dead_letter_queue.awslambda_function_no_dead_letter_queue.awslambda_client",
new=lambda_service,
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_no_dead_letter_queue.awslambda_function_no_dead_letter_queue import (
awslambda_function_no_dead_letter_queue,
)
check = awslambda_function_no_dead_letter_queue()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert function_name in result[0].status_extended
assert queue_arn in result[0].status_extended
assert result[0].resource_id == function_name
assert result[0].resource_arn == function_arn
assert result[0].region == AWS_REGION_EU_WEST_1
@@ -0,0 +1,200 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_EU_WEST_1,
set_mocked_aws_provider,
)
ROLE_POLICY = dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
)
EXTERNAL_ACCOUNT = "999999999999"
def _create_role(iam_client):
return iam_client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=ROLE_POLICY,
)["Role"]["Arn"]
class Test_awslambda_function_using_cross_account_layers:
def test_no_functions(self):
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers.awslambda_client",
new=Lambda(aws_provider),
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers import (
awslambda_function_using_cross_account_layers,
)
check = awslambda_function_using_cross_account_layers()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_function_no_layers(self):
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = _create_role(iam_client)
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-fn-no-layers"
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers.awslambda_client",
new=Lambda(aws_provider),
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers import (
awslambda_function_using_cross_account_layers,
)
check = awslambda_function_using_cross_account_layers()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "does not use any layers" in result[0].status_extended
assert result[0].resource_id == function_name
assert result[0].resource_arn == function_arn
@mock_aws
def test_function_own_account_layer(self):
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = _create_role(iam_client)
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-fn-own-layer"
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import (
Lambda,
Layer,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
lambda_service = Lambda(aws_provider)
# moto does not return Layers in list_functions; inject an own-account layer.
own_layer_arn = f"arn:aws:lambda:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:layer:my-layer:1"
lambda_service.functions[function_arn].layers = [Layer(arn=own_layer_arn)]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers.awslambda_client",
new=lambda_service,
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers import (
awslambda_function_using_cross_account_layers,
)
check = awslambda_function_using_cross_account_layers()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert AWS_ACCOUNT_NUMBER in result[0].status_extended
@mock_aws
def test_function_cross_account_layer(self):
"""Function uses a layer from an external account — FAIL."""
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_arn = _create_role(iam_client)
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
function_name = "test-fn-cross-layer"
function_arn = lambda_client.create_function(
FunctionName=function_name,
Runtime="python3.11",
Role=role_arn,
Handler="index.handler",
Code={"ZipFile": b"file not used"},
)["FunctionArn"]
from prowler.providers.aws.services.awslambda.awslambda_service import (
Lambda,
Layer,
)
cross_layer_arn = f"arn:aws:lambda:{AWS_REGION_EU_WEST_1}:{EXTERNAL_ACCOUNT}:layer:ext-layer:1"
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
lambda_service = Lambda(aws_provider)
# moto does not return Layers; inject a cross-account layer to test FAIL branch.
lambda_service.functions[function_arn].layers = [Layer(arn=cross_layer_arn)]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers.awslambda_client",
new=lambda_service,
),
):
from prowler.providers.aws.services.awslambda.awslambda_function_using_cross_account_layers.awslambda_function_using_cross_account_layers import (
awslambda_function_using_cross_account_layers,
)
check = awslambda_function_using_cross_account_layers()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert cross_layer_arn in result[0].status_extended
assert result[0].resource_id == function_name
assert result[0].resource_arn == function_arn
assert result[0].region == AWS_REGION_EU_WEST_1