diff --git a/.github/workflows/test.ansible.yml b/.github/workflows/test.ansible.yml index e1706adb..c8b141c5 100644 --- a/.github/workflows/test.ansible.yml +++ b/.github/workflows/test.ansible.yml @@ -3,6 +3,10 @@ name: Test-Ansible on: pull_request: branches: [ master ] + paths: + - 'integration/keeper_secrets_manager_ansible/**' + - 'sdk/python/core/**' + - '.github/workflows/test.ansible.yml' jobs: test-ansible: 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 eb41bb7c..dd568545 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 @@ -21,8 +21,6 @@ import random from enum import Enum import traceback -import pickle -import io import base64 import socket @@ -37,6 +35,7 @@ from keeper_secrets_manager_core.core import KSMCache, CreateOptions from keeper_secrets_manager_core.storage import FileKeyValueStorage, InMemoryKeyValueStorage from keeper_secrets_manager_core.utils import generate_password as sdk_generate_password, strtobool + from keeper_secrets_manager_core.dto.dtos import Record as _Record, KeeperFile as _KeeperFile # If keeper_secrets_manager_core is installed, then these will be installed. They are deps. from cryptography.fernet import Fernet @@ -306,16 +305,95 @@ def get_encryption_key(self): return base64.urlsafe_b64encode(kdf.derive(cache_secret.encode())) - def encrypt(self, data): + @staticmethod + def _file_to_dict(keeper_file): + """Serialize a KeeperFile instance to a JSON-safe dictionary.""" + d = { + "name": keeper_file.name, + "title": keeper_file.title, + "type": keeper_file.type, + "last_modified": keeper_file.last_modified, + "size": keeper_file.size, + "f": keeper_file.f, + "file_key": keeper_file.file_key, + "meta_dict": keeper_file.meta_dict, + } + d["record_key_bytes"] = base64.b64encode(keeper_file.record_key_bytes).decode("ascii") \ + if keeper_file.record_key_bytes is not None else None + d["file_data"] = base64.b64encode(keeper_file.file_data).decode("ascii") \ + if keeper_file.file_data is not None else None + return d + @staticmethod + def _file_from_dict(d): + """Reconstruct a KeeperFile instance from a JSON-deserialized dictionary.""" + f = object.__new__(_KeeperFile) + f.name = d["name"] + f.title = d["title"] + f.type = d["type"] + f.last_modified = d["last_modified"] + f.size = d["size"] + f.f = d["f"] + f.file_key = d["file_key"] + f.meta_dict = d["meta_dict"] + f.record_key_bytes = base64.b64decode(d["record_key_bytes"]) \ + if d["record_key_bytes"] is not None else None + f.file_data = base64.b64decode(d["file_data"]) \ + if d["file_data"] is not None else None + return f + + @staticmethod + def _record_to_dict(record): + """Serialize a Record instance to a JSON-safe dictionary.""" + d = { + "uid": record.uid, + "title": record.title, + "type": record.type, + "raw_json": record.raw_json, + "dict": record.dict, + "password": record.password, + "revision": record.revision, + "is_editable": record.is_editable, + "folder_uid": record.folder_uid, + "inner_folder_uid": record.inner_folder_uid, + "links": record.links, + "files": [KeeperAnsible._file_to_dict(f) for f in record.files], + } + d["record_key_bytes"] = base64.b64encode(record.record_key_bytes).decode("ascii") \ + if record.record_key_bytes is not None else None + return d + + @staticmethod + def _record_from_dict(d): + """Reconstruct a Record instance from a JSON-deserialized dictionary.""" + r = object.__new__(_Record) + r.uid = d["uid"] + r.title = d["title"] + r.type = d["type"] + r.raw_json = d["raw_json"] + r.dict = d["dict"] + r.password = d["password"] + r.revision = d["revision"] + r.is_editable = d["is_editable"] + r.folder_uid = d["folder_uid"] + r.inner_folder_uid = d["inner_folder_uid"] + r.links = d["links"] + r.record_key_bytes = base64.b64decode(d["record_key_bytes"]) \ + if d["record_key_bytes"] is not None else None + r.files = [KeeperAnsible._file_from_dict(fd) for fd in d["files"]] + return r + + def encrypt(self, data): secret_key = self.get_encryption_key() - record_fh = io.BytesIO() - pickle.dump(data, record_fh) - return Fernet(secret_key).encrypt(record_fh.getvalue()) + serializable = [KeeperAnsible._record_to_dict(r) for r in data] + json_bytes = json.dumps(serializable).encode("utf-8") + return Fernet(secret_key).encrypt(json_bytes) def decrypt(self, ciphertext): secret_key = self.get_encryption_key() - return pickle.loads(Fernet(secret_key).decrypt(ciphertext)) + json_bytes = Fernet(secret_key).decrypt(ciphertext) + record_dicts = json.loads(json_bytes) + return [KeeperAnsible._record_from_dict(d) for d in record_dicts] @staticmethod def convert_records_into_dict(records): diff --git a/integration/keeper_secrets_manager_ansible/setup.py b/integration/keeper_secrets_manager_ansible/setup.py index 6c5f6da9..cc1b7f8d 100644 --- a/integration/keeper_secrets_manager_ansible/setup.py +++ b/integration/keeper_secrets_manager_ansible/setup.py @@ -16,7 +16,7 @@ setup( name="keeper-secrets-manager-ansible", - version='1.4.0', + version='1.5.0', description="Keeper Secrets Manager plugins for Ansible.", long_description=long_description, long_description_content_type="text/markdown",