diff --git a/integration/keeper_secrets_manager_ansible/README.md b/integration/keeper_secrets_manager_ansible/README.md index e6fdcb6a..779f6a78 100644 --- a/integration/keeper_secrets_manager_ansible/README.md +++ b/integration/keeper_secrets_manager_ansible/README.md @@ -17,6 +17,10 @@ For more information see our official documentation page https://docs.keeper.io/ # Changes +## 1.2.1 +* Add action `keeper_remove` to remove secrets from the Keeper Vault +* Update pinned KSM SDK version to 16.6.2. + ## 1.2.0 * Added action `keeper_cache_records` to cache Keeper Vault records to reduce API calls. diff --git a/integration/keeper_secrets_manager_ansible/ansible_galaxy/keepersecurity/keeper_secrets_manager/README.md b/integration/keeper_secrets_manager_ansible/ansible_galaxy/keepersecurity/keeper_secrets_manager/README.md index 68f5e771..fe57c142 100644 --- a/integration/keeper_secrets_manager_ansible/ansible_galaxy/keepersecurity/keeper_secrets_manager/README.md +++ b/integration/keeper_secrets_manager_ansible/ansible_galaxy/keepersecurity/keeper_secrets_manager/README.md @@ -67,6 +67,7 @@ If you omit the `collections` , you will need to use the full plugin name. * `keepersecurity.keeper_secrets_manager.keeper_get` - Get a value from your vault. * `keepersecurity.keeper_secrets_manager.keeper_set` - Set a value of an existing record in your vault. * `keepersecurity.keeper_secrets_manager.keeper_create` - Create a new record in your vault. +* `keepersecurity.keeper_secrets_manager.keeper_remove` - Remove a record from your vault. * `keepersecurity.keeper_secrets_manager.keeper_password` - Generate a random password. * `keepersecurity.keeper_secrets_manager.keeper_cleanup` - Clean up Keeper related files. * `keepersecurity.keeper_secrets_manager.keeper_info` - Display information about plugin, record and field types. @@ -117,6 +118,10 @@ configuration file or even a playbook. # Changes +## 1.2.1 +* Add action `keeper_remove` to remove secrets from the Keeper Vault +* Update pinned KSM SDK version to 16.6.2. + ## 1.2.0 * Added action `keeper_cache_records` to cache Keeper Vault records to reduce API calls. diff --git a/integration/keeper_secrets_manager_ansible/ansible_galaxy/tower_execution_environment/requirements.txt b/integration/keeper_secrets_manager_ansible/ansible_galaxy/tower_execution_environment/requirements.txt index b5f1f3a3..1228d4f8 100644 --- a/integration/keeper_secrets_manager_ansible/ansible_galaxy/tower_execution_environment/requirements.txt +++ b/integration/keeper_secrets_manager_ansible/ansible_galaxy/tower_execution_environment/requirements.txt @@ -1,3 +1,3 @@ importlib_metadata -keeper-secrets-manager-core>=16.6.0 -keeper-secrets-manager-helper>=1.0.4 \ No newline at end of file +keeper-secrets-manager-core>=16.6.2 +keeper-secrets-manager-helper \ No newline at end of file diff --git a/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__init__.py b/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__init__.py index f122e053..d4d3be4e 100644 --- a/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__init__.py +++ b/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__init__.py @@ -197,10 +197,10 @@ def camel_case(text): display.vvv("Loading keeper config from Ansible vars.") # Since we are getting our variables from Ansible, we want to default using the in memory storage so - # not to leave config files laying around. + # not to leave config files lying around. in_memory_storage = True - # If be have parameter with a Base64 config, use it for the config_option and force + # If we have parameter with a Base64 config, use it for the config_option and force # the config to be in memory. base64_key = KeeperAnsible.keeper_key(KeeperAnsible.KEY_CONFIG_BASE64) if base64_key in task_vars: @@ -388,9 +388,9 @@ def _find_records(records, uids=None, titles=None): uid_map.pop(uid, None) if len(uid_map) > 0: - raise ValueError(f"The following record uid(s) could not be found: {list(uid_map.keys())}") + raise AnsibleError(f"The following record uid(s) could not be found: {list(uid_map.keys())}") if len(title_map) > 0: - raise ValueError(f"The following record title(s) could not be found: {list(title_map.keys())}") + raise AnsibleError(f"The following record title(s) could not be found: {list(title_map.keys())}") return [found_records[x] for x in found_records] @@ -473,6 +473,17 @@ def create_record(self, new_record, shared_folder_uid): return record_uid + def remove_record(self, uids=None, titles=None, cache=None): + + records = self.get_records(cache=cache, uids=uids, titles=titles) + if len(records) > 1 and titles is not None: + raise AnsibleError("Found multiple records for the Title. To fix, make sure records " + "have a unique Title or use a UID.") + + display.vvvvvv(f"removing record UID {records[0].uid}") + + self.client.delete_secret([records[0].uid]) + @staticmethod def _gather_secrets(obj): """ Walk the secret structure and get values. These should just be str, list, and dict. Warn if the SDK @@ -636,13 +647,13 @@ def password_complexity_translation(**kwargs): * allow_symbols - Allow symbols. Default is True * filter_characters - An array of characters not to use. Some servies don't like some characters. - The length is divided by the allowed characters. So with a length of 64, each would get 16 of each characters. + The length is divided by the allowed characters. So with a length of 64, each would get 16 of each character. If the length cannot be unevenly divided, additional will be added to the first allowed character in the above list. """ - # This maps nicer human readable keys to the ones used the records' complexity. + # This maps nicer human-readable keys to the ones used the records' complexity. kwargs_map = [ {"param": "allow_lowercase", "key": "lowercase"}, {"param": "allow_uppercase", "key": "caps"}, @@ -725,7 +736,7 @@ def replacement_char(**kwargs): if new_char not in kwargs.get("filter_characters"): break - # Ok, some user might go crazy and filter out every letter, digit, and symbol and cause an invite loop. + # Ok, some user might go crazy and filter out every letter, digit, and symbol and cause an infinite loop. # If we can't find a good character after 25 attempts, error out. attempt += 1 if attempt > 25: diff --git a/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__main__.py b/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__main__.py index d168bc53..0e8b4d46 100644 --- a/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__main__.py +++ b/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/__main__.py @@ -32,7 +32,7 @@ def _config(): if os.fstat(0) == os.fstat(1): print("\n# Below are the directory paths to action and lookup plugins.", file=sys.stderr) - # Ansible doesn't really work on Windows, however include this anyways for the cleaver DevOp. + # Ansible doesn't really work on Windows, however include this any ways for the cleaver DevOp. if platform.system() == 'Windows': is_power_shell = len(os.getenv('PSModulePath', '').split(os.pathsep)) >= 3 if is_power_shell is True: diff --git a/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/plugins/action_plugins/keeper_remove.py b/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/plugins/action_plugins/keeper_remove.py new file mode 100644 index 00000000..2a54d010 --- /dev/null +++ b/integration/keeper_secrets_manager_ansible/keeper_secrets_manager_ansible/plugins/action_plugins/keeper_remove.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# _ __ +# | |/ /___ ___ _ __ ___ _ _ (R) +# | ' =16.4.1 -keeper-secrets-manager-helper>=1.0.4 +keeper-secrets-manager-core>=16.6.2 +keeper-secrets-manager-helper diff --git a/integration/keeper_secrets_manager_ansible/setup.py b/integration/keeper_secrets_manager_ansible/setup.py index 49a78b7f..bd67108a 100644 --- a/integration/keeper_secrets_manager_ansible/setup.py +++ b/integration/keeper_secrets_manager_ansible/setup.py @@ -9,15 +9,15 @@ long_description = fp.read() install_requires = [ - 'keeper-secrets-manager-core>=16.6.0', - 'keeper-secrets-manager-helper>=1.0.4', + 'keeper-secrets-manager-core>=16.6.2', + 'keeper-secrets-manager-helper', 'importlib_metadata', - 'ansible<8.0.0' + 'ansible' ] setup( name="keeper-secrets-manager-ansible", - version='1.2.0', + version='1.2.1', description="Keeper Secrets Manager plugins for Ansible.", long_description=long_description, long_description_content_type="text/markdown", diff --git a/integration/keeper_secrets_manager_ansible/tests/ansible_example/playbooks/keeper_remove.yml b/integration/keeper_secrets_manager_ansible/tests/ansible_example/playbooks/keeper_remove.yml new file mode 100644 index 00000000..91c187a9 --- /dev/null +++ b/integration/keeper_secrets_manager_ansible/tests/ansible_example/playbooks/keeper_remove.yml @@ -0,0 +1,14 @@ +# vim: set shiftwidth=2 tabstop=2 softtabstop=-1 expandtab: +--- +- name: Keeper Remove + hosts: "my_systems" + gather_facts: no + + tasks: + - name: "Remove By UID" + keeper_remove: + uid: "{{ uid }}" + + - name: "Remove By Title" + keeper_remove: + title: "{{ title }}" diff --git a/integration/keeper_secrets_manager_ansible/tests/ansible_example/playbooks/keeper_remove_cache.yml b/integration/keeper_secrets_manager_ansible/tests/ansible_example/playbooks/keeper_remove_cache.yml new file mode 100644 index 00000000..edb399f0 --- /dev/null +++ b/integration/keeper_secrets_manager_ansible/tests/ansible_example/playbooks/keeper_remove_cache.yml @@ -0,0 +1,36 @@ +# vim: set shiftwidth=2 tabstop=2 softtabstop=-1 expandtab: +--- +- name: Keeper Remove Cache + hosts: "my_systems" + gather_facts: no + + tasks: + - name: Generate a Keeper Record Cache secret + keeper_password: + length: 64 + register: keeper_record_cache_secret + # no_log: True + + - name: Store the Keeper Record Cache secret into variables. + set_fact: + keeper_record_cache_secret: "{{ keeper_record_cache_secret.password }}" + # no_log: True + + - name: Cache records. Will use keeper_record_cache_secret from above. + keeper_cache_records: + uids: + - "{{ uid }}" + titles: + - "{{ title }}" + register: my_records + # no_log: True + + - name: "Remove By UID" + keeper_remove: + cache: "{{ my_records.cache }}" + uid: "{{ uid }}" + + - name: "Remove By Title" + keeper_remove: + cache: "{{ my_records.cache }}" + title: "{{ title }}" diff --git a/integration/keeper_secrets_manager_ansible/tests/keeper_remove_test.py b/integration/keeper_secrets_manager_ansible/tests/keeper_remove_test.py new file mode 100644 index 00000000..8a31fc02 --- /dev/null +++ b/integration/keeper_secrets_manager_ansible/tests/keeper_remove_test.py @@ -0,0 +1,68 @@ +import unittest +from unittest.mock import patch +from keeper_secrets_manager_core.mock import Record, Response +from .ansible_test_framework import AnsibleTestFramework +import tempfile + + + +mock_record_1 = Record(title="Record 1", record_type="login") +mock_record_1.field("password", "PASS 1") +mock_record_2 = Record(title="Record 2", record_type="login") +mock_record_2.field("password", "PASS 2") + +mock_response_1 = Response() +mock_response_1.add_record(record=mock_record_1) +mock_response_1.add_record(record=mock_record_2) +mock_response_1.add_record(record=mock_record_1) +mock_response_1.add_record(record=mock_record_2) + + +mock_response_2 = Response() +mock_response_2.add_record(record=mock_record_1) +mock_response_2.add_record(record=mock_record_2) +mock_response_2.add_record(record=mock_record_1) +mock_response_2.add_record(record=mock_record_2) + + +class KeeperRemoveTest(unittest.TestCase): + + def test_keeper_remove(self): + + with patch(f'keeper_secrets_manager_core.SecretsManager.delete_secret') as mock_delete: + mock_delete.return_value = None + + with tempfile.TemporaryDirectory() as temp_dir: + a = AnsibleTestFramework( + playbook="keeper_remove.yml", + vars={ + "tmp_dir": temp_dir, + "uid": mock_record_1.uid, + "title": mock_record_2.title + }, + mock_responses=[mock_response_1] + ) + result, out, err = a.run() + self.assertEqual(result["ok"], 2, "2 things didn't happen") + self.assertEqual(result["failed"], 0, "failed was not 0") + self.assertEqual(result["changed"], 0, "0 things didn't change") + + def test_keeper_remove_cache(self): + + with patch(f'keeper_secrets_manager_core.SecretsManager.delete_secret') as mock_delete: + mock_delete.return_value = None + + with tempfile.TemporaryDirectory() as temp_dir: + a = AnsibleTestFramework( + playbook="keeper_remove_cache.yml", + vars={ + "tmp_dir": temp_dir, + "uid": mock_record_1.uid, + "title": mock_record_2.title + }, + mock_responses=[mock_response_2] + ) + result, out, err = a.run() + self.assertEqual(result["ok"], 5, "5 things didn't happen") + self.assertEqual(result["failed"], 0, "failed was not 0") + self.assertEqual(result["changed"], 0, "0 things didn't change")