class Crypt(CryptAbstract): def __init__(self, *args, **kwargs): # TODO Remove temporally backward compatibility on future versions crypt_file_key_env = self.__get_updated_crypt_file_key_env() # Temporally backward compatibility self._loader = LoadFile(kwargs.get("path"), crypt_file_key_env, DEFAULT_KEY_FILENAME) super().__init__(*args, **kwargs) def generate_key(self, password: Text, write_to_file: bool = False) -> bytes: byte_password = password.encode() # Convert to type bytes salt = os.urandom(16) kdf = PBKDF2HMAC( algorithm=hashes.SHA512_256(), length=32, salt=salt, iterations=100000, backend=default_backend() ) key = base64.urlsafe_b64encode(kdf.derive(byte_password)) # Can only use kdf once if write_to_file: self._loader.put_file(key, "wb") return key def read_key(self): key = self._loader.get_file() if not key: # TODO Remove temporally backward compatibility on future versions crypt_file_key_env = self.__get_updated_crypt_file_key_env() # Temporally backward compatibility raise FileDoesNotExistException( "Decrypt key {} not exists. You must set a correct env var {} " "or run `pyms crypt create-key` command".format(self._loader.path, crypt_file_key_env) ) return key def encrypt(self, message): key = self.read_key() message = message.encode() f = Fernet(key) encrypted = f.encrypt(message) return encrypted def decrypt(self, encrypted): key = self.read_key() encrypted = encrypted.encode() f = Fernet(key) decrypted = f.decrypt(encrypted) return str(decrypted, encoding="utf-8") def delete_key(self): os.remove(self._loader.get_path_from_env()) @staticmethod def __get_updated_crypt_file_key_env() -> str: result = CRYPT_FILE_KEY_ENVIRONMENT if (os.getenv(CRYPT_FILE_KEY_ENVIRONMENT_LEGACY) is not None) and ( os.getenv(CRYPT_FILE_KEY_ENVIRONMENT) is None ): result = CRYPT_FILE_KEY_ENVIRONMENT_LEGACY return result
class Crypt(CryptAbstract): def __init__(self, *args, **kwargs): self._loader = LoadFile(kwargs.get("path"), CRYPT_FILE_KEY_ENVIRONMENT, DEFAULT_KEY_FILENAME) super().__init__(*args, **kwargs) def generate_key(self, password: Text, write_to_file: bool = False): password = password.encode() # Convert to type bytes salt = os.urandom(16) kdf = PBKDF2HMAC(algorithm=hashes.SHA512_256(), length=32, salt=salt, iterations=100000, backend=default_backend()) key = base64.urlsafe_b64encode( kdf.derive(password)) # Can only use kdf once if write_to_file: self._loader.put_file(key, 'wb') return key def read_key(self): key = self._loader.get_file() if not key: raise FileDoesNotExistException( "Decrypt key {} not exists. You must set a correct env var {} " "or run `pyms crypt create-key` command".format( self._loader.path, CRYPT_FILE_KEY_ENVIRONMENT)) return key def encrypt(self, message): key = self.read_key() message = message.encode() f = Fernet(key) encrypted = f.encrypt(message) return encrypted def decrypt(self, encrypted): key = self.read_key() encrypted = encrypted.encode() f = Fernet(key) decrypted = f.decrypt(encrypted) return str(decrypted, encoding="utf-8") def delete_key(self): os.remove(self._loader.get_path_from_env())
class ConfFile(dict): """Recursive get configuration from dictionary, a config file in JSON or YAML format from a path or `CONFIGMAP_FILE` environment variable. **Atributes:** * path: Path to find the `DEFAULT_CONFIGMAP_FILENAME` and `DEFAULT_KEY_FILENAME` if use encrypted vars * empty_init: Allow blank variables * config: Allow to pass a dictionary to ConfFile without use a file """ _empty_init = False def __init__(self, *args, **kwargs): """ Get configuration from a dictionary(variable `config`), from path (variable `path`) or from environment with the constant `CONFIGMAP_FILE` Set the configuration as upper case to inject the keys in flask config. Flask search for uppercase keys in `app.config.from_object` ```python if key.isupper(): self[key] = getattr(obj, key) ``` """ self._loader = LoadFile(kwargs.get("path"), CONFIGMAP_FILE_ENVIRONMENT, DEFAULT_CONFIGMAP_FILENAME) self._crypt = Crypt(path=kwargs.get("path")) self._empty_init = kwargs.get("empty_init", False) config = kwargs.get("config") if config is None: config = self._loader.get_file(anyconfig.load) if not config: if self._empty_init: config = {} else: path = self._loader.path if self._loader.path else "" raise ConfigDoesNotFoundException( "Configuration file {}not found".format(path + " ")) config = self.set_config(config) super(ConfFile, self).__init__(config) def to_flask(self) -> Dict: return ConfFile(config={k.upper(): v for k, v in self.items()}) def set_config(self, config: Dict) -> Dict: """ Set a dictionary as attributes of ConfFile. This attributes could be access as `ConfFile["attr"]` or ConfFile.attr :param config: a dictionary from `config.yml` :return: """ config = dict(self.normalize_config(config)) pop_encripted_keys = [] for k, v in config.items(): if k.lower().startswith("enc_"): k_not_crypt = re.compile(re.escape('enc_'), re.IGNORECASE) setattr(self, k_not_crypt.sub('', k), self._crypt.decrypt(v)) pop_encripted_keys.append(k) else: setattr(self, k, v) # Delete encrypted keys to prevent decrypt multiple times a element for x in pop_encripted_keys: config.pop(x) return config def normalize_config( self, config: Dict) -> Iterable[Tuple[Text, Union[Dict, Text, bool]]]: for key, item in config.items(): if isinstance(item, dict): item = ConfFile(config=item, empty_init=self._empty_init) yield self.normalize_keys(key), item @staticmethod def normalize_keys(key: Text) -> Text: """The keys will be transformed to a attribute. We need to replace the charactes not valid""" key = key.replace("-", "_") return key def __eq__(self, other): if not isinstance(other, ConfFile) and not isinstance(other, dict): return False return dict(self) == dict(other) def __getattr__(self, name, *args, **kwargs): try: keys = self.normalize_keys(name).split(".") aux_dict = self for k in keys: aux_dict = aux_dict[k] return aux_dict except KeyError: if self._empty_init: return ConfFile(config={}, empty_init=self._empty_init) raise AttrDoesNotExistException( "Variable {} not exist in the config file".format(name)) def reload(self): """ Remove file from memoize variable, return again the content of the file and set the configuration again :return: None """ config_src = self._loader.reload(anyconfig.load) self.set_config(config_src) def __setattr__(self, name, value, *args, **kwargs): super(ConfFile, self).__setattr__(name, value)