-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(integrations): support aws secret manager (#8)
* feat(factory): add aws secret manager * refactor(integrations): abstract pagination logic * style(integrations): remove space * feat(integrations): add secret manager support * docs(readme): add integrations section * test(integrations): verify secret manager fetch secrets * chore(makefile): add additional runs for each integration
- Loading branch information
Showing
9 changed files
with
211 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import logging | ||
from typing import Optional | ||
|
||
from fuzzy_secret_stdout.models import SecretStoreItem | ||
from fuzzy_secret_stdout.integrations import SecretIntegration | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
class AWSSecretManager(SecretIntegration): | ||
|
||
def __init__(self, boto_client) -> None: | ||
self._boto_client = boto_client | ||
|
||
def fetch_all(self, max_batch_results: Optional[int] = 3) -> list[SecretStoreItem]: | ||
|
||
def inner(boto_client, **kwargs): | ||
return boto_client.list_secrets(**kwargs) | ||
|
||
return self._paginate_results(inner, max_batch_results, 'SecretList', 'Name', 'secretmanager') | ||
|
||
def fetch_secrets(self, item_names: list[str]) -> list[SecretStoreItem]: | ||
result = self._boto_client.batch_get_secret_value(SecretIdList=item_names) | ||
result = [SecretStoreItem(x['Name'], x['SecretString']) for x in result['SecretValues']] | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
from unittest.mock import Mock, call | ||
from fuzzy_secret_stdout.integrations.aws_secret_manager import AWSSecretManager | ||
from fuzzy_secret_stdout.models import SecretStoreItem | ||
|
||
import pytest | ||
|
||
@pytest.mark.parametrize("list_secrets_return", [ | ||
pytest.param({}, id='empty_response'), | ||
pytest.param({'SecretList': []}, id='empty_parameters') | ||
]) | ||
def test_fetch_all_no_parameters(list_secrets_return: dict): | ||
|
||
mock_secret_man: Mock = Mock() | ||
mock_secret_man.list_secrets.return_value = list_secrets_return | ||
|
||
integration = AWSSecretManager(mock_secret_man) | ||
result = integration.fetch_all() | ||
|
||
assert result == [] | ||
assert mock_secret_man.list_secrets.call_args_list == [call(MaxResults=3)] | ||
|
||
|
||
@pytest.mark.parametrize("max_results", [ | ||
3, | ||
5, | ||
10 | ||
]) | ||
def test_fetch_all_max_results_override(max_results: int): | ||
mock_secret_man: Mock = Mock() | ||
mock_secret_man.list_secrets.return_value = [] | ||
|
||
integration = AWSSecretManager(mock_secret_man) | ||
integration.fetch_all(max_batch_results=max_results) | ||
|
||
assert mock_secret_man.list_secrets.call_args_list == [call(MaxResults=max_results)] | ||
|
||
def test_fetch_all__keys_no_pagination(): | ||
|
||
mock_secret_man: Mock = Mock() | ||
mock_secret_man.list_secrets.return_value = { | ||
'SecretList': [ | ||
{'Name': 'param1'}, | ||
{'Name': 'param2'}, | ||
{'Name': 'param3'}, | ||
] | ||
} | ||
|
||
integration = AWSSecretManager(mock_secret_man) | ||
result = integration.fetch_all() | ||
|
||
assert mock_secret_man.list_secrets.call_args_list == [call(MaxResults=3)] | ||
assert result == [ | ||
SecretStoreItem(key='param1'), | ||
SecretStoreItem(key='param2'), | ||
SecretStoreItem(key='param3') | ||
] | ||
|
||
def test_fetch_all_keys_pagination(): | ||
|
||
mock_secret_man: Mock = Mock() | ||
mock_secret_man.list_secrets.side_effect = [ | ||
# initial call | ||
{ | ||
'SecretList': [ {'Name': 'param1'}], | ||
'NextToken': 'token1' | ||
}, | ||
# second call | ||
{ | ||
'SecretList': [ {'Name': 'param2'}, {'Name': 'param3'} ], | ||
'NextToken': 'token2' | ||
}, | ||
# final call | ||
{ | ||
'SecretList': [ | ||
{'Name': 'param4'} | ||
] | ||
}, | ||
] | ||
|
||
integration = AWSSecretManager(mock_secret_man) | ||
result = integration.fetch_all() | ||
|
||
assert result == [ | ||
SecretStoreItem(key='param1'), | ||
SecretStoreItem(key='param2'), | ||
SecretStoreItem(key='param3'), | ||
SecretStoreItem(key='param4') | ||
] | ||
|
||
assert mock_secret_man.list_secrets.call_args_list == [call(MaxResults=3), call(NextToken='token1', MaxResults=3), call(NextToken='token2', MaxResults=3)] | ||
|
||
def test_fetch_secrets(): | ||
input = ['param1'] | ||
|
||
mock_secret_man: Mock = Mock() | ||
mock_secret_man.batch_get_secret_value.return_value = { | ||
'SecretValues': [ | ||
{'Name': 'param1', 'SecretString': 'value1'} | ||
] | ||
} | ||
|
||
integration = AWSSecretManager(mock_secret_man) | ||
result = integration.fetch_secrets(input) | ||
|
||
assert result == [SecretStoreItem(key='param1', value='value1')] | ||
assert mock_secret_man.batch_get_secret_value.call_args_list == [call(SecretIdList=['param1'])] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters