Skip to content

Commit 149c5e5

Browse files
feat(parameters) - environment default variables
1 parent bac3939 commit 149c5e5

File tree

5 files changed

+86
-10
lines changed

5 files changed

+86
-10
lines changed

aws_lambda_powertools/shared/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED"
2424

25+
PARAMETERS_DEFAULT_DECRYPT: str = "POWERTOOLS_PARAMETERS_SSM_DECRYPT"
26+
PARAMETERS_MAX_AGE: str = "POWERTOOLS_PARAMETERS_MAX_AGE"
27+
2528
LOGGER_LAMBDA_CONTEXT_KEYS = [
2629
"function_arn",
2730
"function_memory_size",

aws_lambda_powertools/utilities/parameters/ssm.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
"""
44
from __future__ import annotations
55

6+
import os
67
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, overload
78

89
import boto3
910
from botocore.config import Config
1011
from typing_extensions import Literal
1112

12-
from aws_lambda_powertools.shared.functions import slice_dictionary
13+
from aws_lambda_powertools.shared import constants
14+
from aws_lambda_powertools.shared.functions import (
15+
resolve_truthy_env_var_choice,
16+
slice_dictionary,
17+
)
1318

1419
from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider, transform_value
1520
from .exceptions import GetParameterError
@@ -112,7 +117,7 @@ def get( # type: ignore[override]
112117
name: str,
113118
max_age: int = DEFAULT_MAX_AGE_SECS,
114119
transform: TransformOptions = None,
115-
decrypt: bool = False,
120+
decrypt: Optional[bool] = None,
116121
force_fetch: bool = False,
117122
**sdk_options,
118123
) -> Optional[Union[str, dict, bytes]]:
@@ -145,6 +150,11 @@ def get( # type: ignore[override]
145150
When the parameter provider fails to transform a parameter value.
146151
"""
147152

153+
# Resolving if will use the value passed by parameter or the environment
154+
decrypt = resolve_truthy_env_var_choice(
155+
env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt
156+
)
157+
148158
# Add to `decrypt` sdk_options to we can have an explicit option for this
149159
sdk_options["decrypt"] = decrypt
150160

@@ -212,7 +222,7 @@ def get_parameters_by_name(
212222
self,
213223
parameters: Dict[str, Dict],
214224
transform: TransformOptions = None,
215-
decrypt: bool = False,
225+
decrypt: Optional[bool] = None,
216226
max_age: int = DEFAULT_MAX_AGE_SECS,
217227
raise_on_error: bool = True,
218228
) -> Dict[str, str] | Dict[str, bytes] | Dict[str, dict]:
@@ -259,6 +269,12 @@ def get_parameters_by_name(
259269
260270
When "_errors" reserved key is in parameters to be fetched from SSM.
261271
"""
272+
273+
# Resolving if will use the default value (False), the value passed by parameter or the environment variable
274+
decrypt = resolve_truthy_env_var_choice(
275+
env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt
276+
)
277+
262278
# Init potential batch/decrypt batch responses and errors
263279
batch_ret: Dict[str, Any] = {}
264280
decrypt_ret: Dict[str, Any] = {}
@@ -487,7 +503,7 @@ def _raise_if_errors_key_is_present(parameters: Dict, reserved_parameter: str, r
487503
def get_parameter(
488504
name: str,
489505
transform: Optional[str] = None,
490-
decrypt: bool = False,
506+
decrypt: Optional[bool] = None,
491507
force_fetch: bool = False,
492508
max_age: int = DEFAULT_MAX_AGE_SECS,
493509
**sdk_options,
@@ -543,6 +559,11 @@ def get_parameter(
543559
if "ssm" not in DEFAULT_PROVIDERS:
544560
DEFAULT_PROVIDERS["ssm"] = SSMProvider()
545561

562+
# Resolving if will use the default value (False), the value passed by parameter or the environment variable
563+
decrypt = resolve_truthy_env_var_choice(
564+
env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt
565+
)
566+
546567
# Add to `decrypt` sdk_options to we can have an explicit option for this
547568
sdk_options["decrypt"] = decrypt
548569

@@ -555,7 +576,7 @@ def get_parameters(
555576
path: str,
556577
transform: Optional[str] = None,
557578
recursive: bool = True,
558-
decrypt: bool = False,
579+
decrypt: Optional[bool] = None,
559580
force_fetch: bool = False,
560581
max_age: int = DEFAULT_MAX_AGE_SECS,
561582
raise_on_transform_error: bool = False,
@@ -617,6 +638,11 @@ def get_parameters(
617638
if "ssm" not in DEFAULT_PROVIDERS:
618639
DEFAULT_PROVIDERS["ssm"] = SSMProvider()
619640

641+
# Resolving if will use the default value (False), the value passed by parameter or the environment variable
642+
decrypt = resolve_truthy_env_var_choice(
643+
env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt
644+
)
645+
620646
sdk_options["recursive"] = recursive
621647
sdk_options["decrypt"] = decrypt
622648

@@ -634,7 +660,7 @@ def get_parameters(
634660
def get_parameters_by_name(
635661
parameters: Dict[str, Dict],
636662
transform: None = None,
637-
decrypt: bool = False,
663+
decrypt: Optional[bool] = None,
638664
max_age: int = DEFAULT_MAX_AGE_SECS,
639665
raise_on_error: bool = True,
640666
) -> Dict[str, str]:
@@ -645,7 +671,7 @@ def get_parameters_by_name(
645671
def get_parameters_by_name(
646672
parameters: Dict[str, Dict],
647673
transform: Literal["binary"],
648-
decrypt: bool = False,
674+
decrypt: Optional[bool] = None,
649675
max_age: int = DEFAULT_MAX_AGE_SECS,
650676
raise_on_error: bool = True,
651677
) -> Dict[str, bytes]:
@@ -656,7 +682,7 @@ def get_parameters_by_name(
656682
def get_parameters_by_name(
657683
parameters: Dict[str, Dict],
658684
transform: Literal["json"],
659-
decrypt: bool = False,
685+
decrypt: Optional[bool] = None,
660686
max_age: int = DEFAULT_MAX_AGE_SECS,
661687
raise_on_error: bool = True,
662688
) -> Dict[str, Dict[str, Any]]:
@@ -667,7 +693,7 @@ def get_parameters_by_name(
667693
def get_parameters_by_name(
668694
parameters: Dict[str, Dict],
669695
transform: Literal["auto"],
670-
decrypt: bool = False,
696+
decrypt: Optional[bool] = None,
671697
max_age: int = DEFAULT_MAX_AGE_SECS,
672698
raise_on_error: bool = True,
673699
) -> Union[Dict[str, str], Dict[str, dict]]:
@@ -677,7 +703,7 @@ def get_parameters_by_name(
677703
def get_parameters_by_name(
678704
parameters: Dict[str, Any],
679705
transform: TransformOptions = None,
680-
decrypt: bool = False,
706+
decrypt: Optional[bool] = None,
681707
max_age: int = DEFAULT_MAX_AGE_SECS,
682708
raise_on_error: bool = True,
683709
) -> Union[Dict[str, str], Dict[str, bytes], Dict[str, dict]]:
@@ -732,6 +758,11 @@ def get_parameters_by_name(
732758
# NOTE: Decided against using multi-thread due to single-thread outperforming in 128M and 1G + timeout risk
733759
# see: https://github.com/awslabs/aws-lambda-powertools-python/issues/1040#issuecomment-1299954613
734760

761+
# Resolving if will use the default value (False), the value passed by parameter or the environment variable
762+
decrypt = resolve_truthy_env_var_choice(
763+
env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt
764+
)
765+
735766
# Only create the provider if this function is called at least once
736767
if "ssm" not in DEFAULT_PROVIDERS:
737768
DEFAULT_PROVIDERS["ssm"] = SSMProvider()

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai
706706
| **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger) | `false` |
707707
| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logger) | `0` |
708708
| **POWERTOOLS_LOG_DEDUPLICATION_DISABLED** | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger) | `false` |
709+
| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](.//utilities/parameters/#ssmprovider) | `false` |
709710
| **POWERTOOLS_DEV** | Increases verbosity across utilities | Multiple; see [POWERTOOLS_DEV effect below](#increasing-verbosity-across-utilities) | `false` |
710711
| **LOG_LEVEL** | Sets logging level | [Logging](./core/logger) | `INFO` |
711712

docs/utilities/parameters.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen
179179

180180
You can create `SecureString` parameters, which are parameters that have a plaintext parameter name and an encrypted parameter value. If you don't use the `decrypt` argument, you will get an encrypted value. Read [here](https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html) about best practices using KMS to secure your parameters.
181181

182+
???+ tip
183+
If you want to always decrypt parameters, you can set the `POWERTOOLS_PARAMETERS_SSM_DECRYPT=true` environment variable. **This will override the default value of `false` but can be overridden by the `decrypt` parameter**.
184+
182185
=== "builtin_provider_ssm_with_decrypt.py"
183186
```python hl_lines="6 10 16"
184187
--8<-- "examples/parameters/src/builtin_provider_ssm_with_decrypt.py"

tests/functional/test_utilities_parameters.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,44 @@ def test_ssm_provider_get_with_custom_client(mock_name, mock_value, mock_version
547547
stubber.deactivate()
548548

549549

550+
def test_ssm_provider_get_with_decrypt_environment_variable(monkeypatch, mock_name, mock_value, mock_version, config):
551+
"""
552+
Test SSMProvider.get() with decrypt value replaced by environment variable
553+
"""
554+
555+
# Setting environment variable to override the default value
556+
monkeypatch.setenv("POWERTOOLS_PARAMETERS_SSM_DECRYPT", "true")
557+
558+
# Create a new provider
559+
provider = parameters.SSMProvider(config=config)
560+
561+
# Stub the boto3 client
562+
stubber = stub.Stubber(provider.client)
563+
response = {
564+
"Parameter": {
565+
"Name": mock_name,
566+
"Type": "String",
567+
"Value": mock_value,
568+
"Version": mock_version,
569+
"Selector": f"{mock_name}:{mock_version}",
570+
"SourceResult": "string",
571+
"LastModifiedDate": datetime(2015, 1, 1),
572+
"ARN": f"arn:aws:ssm:us-east-2:111122223333:parameter/{mock_name}",
573+
}
574+
}
575+
expected_params = {"Name": mock_name, "WithDecryption": True}
576+
stubber.add_response("get_parameter", response, expected_params)
577+
stubber.activate()
578+
579+
try:
580+
value = provider.get(mock_name)
581+
582+
assert value == mock_value
583+
stubber.assert_no_pending_responses()
584+
finally:
585+
stubber.deactivate()
586+
587+
550588
def test_ssm_provider_get_default_config(monkeypatch, mock_name, mock_value, mock_version):
551589
"""
552590
Test SSMProvider.get() without specifying the config

0 commit comments

Comments
 (0)