|
diff --git a/docs/searchindex.js b/docs/searchindex.js
index 7597596..77f1855 100644
--- a/docs/searchindex.js
+++ b/docs/searchindex.js
@@ -1 +1 @@
-Search.setIndex({"docnames": ["README", "index"], "filenames": ["README.md", "index.rst"], "titles": ["VaultAPI", "Welcome to VaultAPI\u2019s documentation!"], "terms": {"lightweight": 0, "api": 0, "store": [0, 1], "retriev": [0, 1], "secret": [0, 1], "from": [0, 1], "an": [0, 1], "encrypt": 0, "databas": 0, "platform": 0, "support": [0, 1], "deploy": 0, "recommend": 0, "instal": 0, "python": 0, "3": 0, "10": [0, 1], "11": 0, "us": [0, 1], "dedic": 0, "virtual": 0, "m": 0, "pip": 0, "initi": 0, "id": 0, "import": 0, "__name__": 0, "__main__": 0, "start": [0, 1], "cli": 0, "help": 0, "usag": 0, "instruct": 0, "sourc": 0, "env": [0, 1], "file": [0, 1], "By": 0, "default": [0, 1], "look": 0, "current": 0, "work": 0, "directori": 0, "host": [0, 1], "hostnam": [0, 1], "server": [0, 1], "port": [0, 1], "number": [0, 1], "worker": [0, 1], "uvicorn": [0, 1], "apikei": [0, 1], "kei": [0, 1], "authent": 0, "access": [0, 1], "encod": 0, "decod": 0, "datastor": 0, "filepath": [0, 1], "rate": [0, 1], "_": 0, "limit": [0, 1], "list": [0, 1], "dictionari": [0, 1], "max_request": [0, 1], "second": [0, 1], "appli": [0, 1], "auto": 0, "gener": [0, 1], "valu": [0, 1], "thi": [0, 1], "decrypt": 0, "keygen": 0, "cryptographi": [0, 1], "fernet": [0, 1], "print": 0, "generate_kei": 0, "docstr": 0, "format": [0, 1], "googl": 0, "style": 0, "convent": 0, "pep": 0, "8": 0, "isort": 0, "requir": [0, 1], "gitvers": 0, "revers": 0, "f": 0, "release_not": 0, "rst": 0, "t": 0, "pre": 0, "commit": 0, "ensur": 0, "run": 0, "pytest": 0, "valid": [0, 1], "hyperlink": 0, "all": [0, 1], "markdown": 0, "includ": 0, "wiki": 0, "page": [0, 1], "sphinx": 0, "5": 0, "1": [0, 1], "recommonmark": 0, "http": 0, "org": 0, "project": 0, "hub": 0, "com": 0, "r": 0, "thevickypedia": 0, "github": 0, "io": 0, "vignesh": 0, "rao": 0, "under": 0, "mit": 0, "kick": 1, "off": 1, "environ": 1, "variabl": 1, "code": 1, "standard": 1, "releas": 1, "note": 1, "lint": 1, "pypi": 1, "packag": 1, "docker": 1, "imag": 1, "runbook": 1, "licens": 1, "copyright": 1, "enable_cor": 1, "none": 1, "enabl": 1, "cor": 1, "polici": 1, "kwarg": 1, "starter": 1, "function": 1, "which": 1, "trigger": 1, "keyword": 1, "argument": 1, "env_fil": 1, "load": 1, "auth": 1, "handl": 1, "error": 1, "rate_limit": 1, "log_config": 1, "log": 1, "configur": 1, "dict": 1, "yaml": 1, "yml": 1, "json": 1, "ini": 1, "epoch": 1, "async": 1, "request": 1, "httpauthorizationcredenti": 1, "httpbearer": 1, "paramet": 1, "take": 1, "author": 1, "header": 1, "token": 1, "basic": 1, "rais": 1, "apirespons": 1, "401": 1, "If": 1, "i": 1, "invalid": 1, "403": 1, "address": 1, "forbidden": 1, "table_exist": 1, "table_nam": 1, "str": 1, "bool": 1, "check": 1, "exist": 1, "name": 1, "create_t": 1, "column": 1, "union": 1, "tupl": 1, "creat": 1, "ha": 1, "get_secret": 1, "where": 1, "return": 1, "type": 1, "get_tabl": 1, "pair": 1, "particular": 1, "ar": 1, "put_secret": 1, "add": 1, "remove_secret": 1, "remov": 1, "drop_tabl": 1, "drop": 1, "status_cod": 1, "int": 1, "detail": 1, "ani": 1, "option": 1, "custom": 1, "httpexcept": 1, "fastapi": 1, "wrap": 1, "respons": 1, "class": 1, "basemodel": 1, "object": 1, "set": 1, "session": 1, "inform": 1, "info": 1, "rp": 1, "allowed_origin": 1, "config": 1, "allow": 1, "arbitrari": 1, "arbitrary_types_allow": 1, "true": 1, "envconfig": 1, "baseset": 1, "path": 1, "url": 1, "allowed_ip_rang": 1, "classmethod": 1, "parse_allowed_origin": 1, "origin": 1, "parse_allowed_ip_rang": 1, "ip": 1, "rang": 1, "whitelist": 1, "parse_apikei": 1, "pars": 1, "complex": 1, "parse_api_secret": 1, "compat": 1, "from_env_fil": 1, "instanc": 1, "extra": 1, "ignor": 1, "hide_input_in_error": 1, "complexity_check": 1, "verifi": 1, "strength": 1, "A": 1, "consid": 1, "strong": 1, "least": 1, "32": 1, "charact": 1, "digit": 1, "symbol": 1, "uppercas": 1, "letter": 1, "lowercas": 1, "assertionerror": 1, "when": 1, "abov": 1, "condit": 1, "fail": 1, "match": 1, "timeout": 1, "connect": 1, "instanti": 1, "cursor": 1, "alia": 1, "deletesecret": 1, "delet": 1, "call": 1, "putsecret": 1, "put": 1, "_get_identifi": 1, "uniqu": 1, "identifi": 1, "incom": 1, "init": 1, "exce": 1, "given": 1, "The": 1, "429": 1, "too": 1, "mani": 1, "retrieve_secret": 1, "multipl": 1, "whole": 1, "have": 1, "depend": 1, "arg": 1, "refer": 1, "httpstatu": 1, "statu": 1, "time": 1, "comma": 1, "separ": 1, "data": 1, "bodi": 1, "delete_secret": 1, "new": 1, "health": 1, "healthcheck": 1, "endpoint": 1, "doc": 1, "redirectrespons": 1, "redirect": 1, "user": 1, "get_all_rout": 1, "apirout": 1, "get": 1, "ad": 1, "envfile_load": 1, "filenam": 1, "o": 1, "pathlik": 1, "base": 1, "filetyp": 1, "var": 1, "load_env": 1, "merg": 1, "give": 1, "prioriti": 1, "partial": 1, "through": 1, "dotenv_to_t": 1, "dotenv_fil": 1, "drop_exist": 1, "fals": 1, "dot": 1, "boolean": 1, "flag": 1, "index": 1, "modul": 1, "search": 1}, "objects": {"vaultapi": [[1, 0, 0, "-", "auth"], [1, 0, 0, "-", "database"], [1, 0, 0, "-", "exceptions"], [1, 0, 0, "-", "main"], [1, 0, 0, "-", "models"], [1, 0, 0, "-", "rate_limit"], [1, 0, 0, "-", "routes"], [1, 0, 0, "-", "squire"], [1, 0, 0, "-", "util"]], "vaultapi.auth": [[1, 1, 1, "", "EPOCH"], [1, 1, 1, "", "validate"]], "vaultapi.database": [[1, 1, 1, "", "create_table"], [1, 1, 1, "", "drop_table"], [1, 1, 1, "", "get_secret"], [1, 1, 1, "", "get_table"], [1, 1, 1, "", "put_secret"], [1, 1, 1, "", "remove_secret"], [1, 1, 1, "", "table_exists"]], "vaultapi.exceptions": [[1, 2, 1, "", "APIResponse"]], "vaultapi.main": [[1, 1, 1, "", "enable_cors"], [1, 1, 1, "", "start"]], "vaultapi.models": [[1, 3, 1, "", "Database"], [1, 3, 1, "", "EnvConfig"], [1, 3, 1, "", "RateLimit"], [1, 3, 1, "", "Session"], [1, 1, 1, "", "complexity_checker"], [1, 4, 1, "", "database"], [1, 4, 1, "", "env"]], "vaultapi.models.EnvConfig": [[1, 3, 1, "", "Config"], [1, 4, 1, "", "allowed_ip_range"], [1, 4, 1, "", "allowed_origins"], [1, 4, 1, "", "apikey"], [1, 4, 1, "", "database"], [1, 5, 1, "", "from_env_file"], [1, 4, 1, "", "host"], [1, 4, 1, "", "log_config"], [1, 5, 1, "", "parse_allowed_ip_range"], [1, 5, 1, "", "parse_allowed_origins"], [1, 5, 1, "", "parse_api_secret"], [1, 5, 1, "", "parse_apikey"], [1, 4, 1, "", "port"], [1, 4, 1, "", "rate_limit"], [1, 4, 1, "", "secret"], [1, 4, 1, "", "workers"]], "vaultapi.models.EnvConfig.Config": [[1, 4, 1, "", "arbitrary_types_allowed"], [1, 4, 1, "", "extra"], [1, 4, 1, "", "hide_input_in_errors"]], "vaultapi.models.RateLimit": [[1, 4, 1, "", "max_requests"], [1, 4, 1, "", "seconds"]], "vaultapi.models.Session": [[1, 3, 1, "", "Config"], [1, 4, 1, "", "allowed_origins"], [1, 4, 1, "", "fernet"], [1, 4, 1, "", "info"], [1, 4, 1, "", "rps"]], "vaultapi.models.Session.Config": [[1, 4, 1, "", "arbitrary_types_allowed"]], "vaultapi.payload": [[1, 3, 1, "", "DeleteSecret"], [1, 3, 1, "", "PutSecret"]], "vaultapi.payload.DeleteSecret": [[1, 4, 1, "", "key"], [1, 4, 1, "", "table_name"]], "vaultapi.payload.PutSecret": [[1, 4, 1, "", "key"], [1, 4, 1, "", "table_name"], [1, 4, 1, "", "value"]], "vaultapi.rate_limit": [[1, 3, 1, "", "RateLimiter"], [1, 1, 1, "", "_get_identifier"]], "vaultapi.rate_limit.RateLimiter": [[1, 5, 1, "", "init"]], "vaultapi.routes": [[1, 1, 1, "", "create_table"], [1, 1, 1, "", "delete_secret"], [1, 1, 1, "", "docs"], [1, 1, 1, "", "get_all_routes"], [1, 1, 1, "", "get_secret"], [1, 1, 1, "", "get_secrets"], [1, 1, 1, "", "get_table"], [1, 1, 1, "", "health"], [1, 1, 1, "", "put_secret"], [1, 1, 1, "", "put_secrets"], [1, 1, 1, "", "retrieve_secret"], [1, 1, 1, "", "retrieve_secrets"]], "vaultapi.squire": [[1, 1, 1, "", "envfile_loader"], [1, 1, 1, "", "load_env"]], "vaultapi.util": [[1, 1, 1, "", "dotenv_to_table"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:exception", "3": "py:class", "4": "py:attribute", "5": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "exception", "Python exception"], "3": ["py", "class", "Python class"], "4": ["py", "attribute", "Python attribute"], "5": ["py", "method", "Python method"]}, "titleterms": {"vaultapi": [0, 1], "kick": 0, "off": 0, "environ": 0, "variabl": 0, "code": 0, "standard": 0, "releas": 0, "note": 0, "lint": 0, "pypi": 0, "packag": 0, "docker": 0, "imag": 0, "runbook": 0, "licens": 0, "copyright": 0, "welcom": 1, "": 1, "document": 1, "content": 1, "main": 1, "authent": 1, "databas": 1, "except": 1, "model": 1, "payload": 1, "ratelimit": 1, "api": 1, "rout": 1, "squir": 1, "util": 1, "indic": 1, "tabl": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 56}})
\ No newline at end of file
+Search.setIndex({"docnames": ["README", "index"], "filenames": ["README.md", "index.rst"], "titles": ["VaultAPI", "Welcome to VaultAPI\u2019s documentation!"], "terms": {"lightweight": 0, "api": 0, "store": [0, 1], "retriev": [0, 1], "secret": [0, 1], "from": [0, 1], "an": [0, 1], "encrypt": [0, 1], "databas": 0, "platform": 0, "support": [0, 1], "deploy": 0, "recommend": 0, "instal": 0, "python": 0, "3": 0, "10": [0, 1], "11": 0, "us": [0, 1], "dedic": 0, "virtual": 0, "m": 0, "pip": 0, "initi": 0, "id": 0, "import": 0, "__name__": 0, "__main__": 0, "start": [0, 1], "cli": 0, "help": 0, "usag": 0, "instruct": 0, "sourc": 0, "env": [0, 1], "file": [0, 1], "By": 0, "default": [0, 1], "look": 0, "current": 0, "work": 0, "directori": 0, "mandatori": 0, "apikei": [0, 1], "kei": [0, 1], "authent": 0, "access": [0, 1], "encod": [0, 1], "decod": 0, "datastor": 0, "option": [0, 1], "transit": [0, 1], "_": 0, "length": [0, 1], "ae": [0, 1], "32": [0, 1], "filepath": [0, 1], "db": 0, "host": [0, 1], "hostnam": [0, 1], "server": [0, 1], "0": 0, "OR": 0, "localhost": 0, "port": [0, 1], "number": [0, 1], "9010": 0, "worker": [0, 1], "uvicorn": [0, 1], "1": [0, 1], "rate": [0, 1], "limit": [0, 1], "list": [0, 1], "dictionari": [0, 1], "max_request": [0, 1], "second": [0, 1], "appli": [0, 1], "5req": 0, "2": 0, "AND": 0, "10req": 0, "30": 0, "without": 0, "log": [0, 1], "config": [0, 1], "valu": [0, 1], "pair": [0, 1], "allow": [0, 1], "origin": [0, 1], "ar": [0, 1], "ip": [0, 1], "rang": [0, 1], "i": [0, 1], "eg": 0, "112": 0, "8": 0, "210": 0, "auto": 0, "gener": [0, 1], "thi": [0, 1], "decrypt": [0, 1], "keygen": 0, "cryptographi": [0, 1], "fernet": [0, 1], "print": 0, "generate_kei": 0, "docstr": 0, "format": [0, 1], "googl": 0, "style": 0, "convent": 0, "pep": 0, "isort": 0, "requir": [0, 1], "gitvers": 0, "revers": 0, "f": 0, "release_not": 0, "rst": 0, "t": 0, "pre": 0, "commit": 0, "ensur": 0, "run": 0, "pytest": 0, "valid": [0, 1], "hyperlink": 0, "all": [0, 1], "markdown": 0, "includ": 0, "wiki": 0, "page": [0, 1], "sphinx": 0, "5": 0, "recommonmark": 0, "http": 0, "org": 0, "project": 0, "hub": 0, "com": 0, "r": 0, "thevickypedia": 0, "github": 0, "io": 0, "vignesh": 0, "rao": 0, "under": 0, "mit": 0, "kick": 1, "off": 1, "environ": 1, "variabl": 1, "code": 1, "standard": 1, "releas": 1, "note": 1, "lint": 1, "pypi": 1, "packag": 1, "docker": 1, "imag": 1, "runbook": 1, "licens": 1, "copyright": 1, "enable_cor": 1, "none": 1, "enabl": 1, "cor": 1, "polici": 1, "kwarg": 1, "starter": 1, "function": 1, "which": 1, "trigger": 1, "keyword": 1, "argument": 1, "env_fil": 1, "load": 1, "auth": 1, "handl": 1, "error": 1, "rate_limit": 1, "log_config": 1, "configur": 1, "dict": 1, "yaml": 1, "yml": 1, "json": 1, "ini": 1, "epoch": 1, "async": 1, "request": 1, "httpauthorizationcredenti": 1, "httpbearer": 1, "paramet": 1, "take": 1, "author": 1, "header": 1, "token": 1, "basic": 1, "rais": 1, "apirespons": 1, "401": 1, "If": 1, "invalid": 1, "403": 1, "address": 1, "forbidden": 1, "table_exist": 1, "table_nam": 1, "str": 1, "bool": 1, "check": 1, "exist": 1, "name": 1, "create_t": 1, "column": 1, "union": 1, "tupl": 1, "creat": 1, "ha": 1, "get_secret": 1, "where": 1, "return": 1, "type": 1, "get_tabl": 1, "particular": 1, "put_secret": 1, "add": 1, "remove_secret": 1, "remov": 1, "drop_tabl": 1, "drop": 1, "status_cod": 1, "int": 1, "detail": 1, "ani": 1, "custom": 1, "httpexcept": 1, "fastapi": 1, "wrap": 1, "respons": 1, "class": 1, "basemodel": 1, "object": 1, "set": 1, "session": 1, "inform": 1, "aes_kei": 1, "bytestr": 1, "info": 1, "rp": 1, "allowed_origin": 1, "arbitrari": 1, "arbitrary_types_allow": 1, "true": 1, "envconfig": 1, "baseset": 1, "transit_key_length": 1, "path": 1, "url": 1, "allowed_ip_rang": 1, "classmethod": 1, "validate_allowed_origin": 1, "validate_allowed_ip_rang": 1, "whitelist": 1, "validate_apikei": 1, "complex": 1, "validate_api_secret": 1, "compat": 1, "from_env_fil": 1, "instanc": 1, "extra": 1, "ignor": 1, "hide_input_in_error": 1, "complexity_check": 1, "verifi": 1, "strength": 1, "A": 1, "consid": 1, "strong": 1, "least": 1, "charact": 1, "digit": 1, "symbol": 1, "uppercas": 1, "letter": 1, "lowercas": 1, "assertionerror": 1, "when": 1, "abov": 1, "condit": 1, "fail": 1, "match": 1, "timeout": 1, "connect": 1, "instanti": 1, "cursor": 1, "alia": 1, "deletesecret": 1, "delet": 1, "call": 1, "putsecret": 1, "put": 1, "_get_identifi": 1, "uniqu": 1, "identifi": 1, "incom": 1, "init": 1, "exce": 1, "given": 1, "The": 1, "429": 1, "too": 1, "mani": 1, "retrieve_secret": 1, "multipl": 1, "whole": 1, "have": 1, "depend": 1, "arg": 1, "refer": 1, "httpstatu": 1, "statu": 1, "time": 1, "comma": 1, "separ": 1, "data": 1, "bodi": 1, "delete_secret": 1, "new": 1, "health": 1, "healthcheck": 1, "endpoint": 1, "doc": 1, "redirectrespons": 1, "redirect": 1, "user": 1, "get_all_rout": 1, "apirout": 1, "get": 1, "ad": 1, "envfile_load": 1, "filenam": 1, "o": 1, "pathlik": 1, "base": 1, "filetyp": 1, "var": 1, "load_env": 1, "merg": 1, "give": 1, "prioriti": 1, "partial": 1, "through": 1, "modul": 1, "perform": 1, "secur": 1, "transmit": 1, "client": 1, "side": 1, "string_to_aes_kei": 1, "input_str": 1, "key_length": 1, "hash": 1, "string": 1, "size": 1, "dure": 1, "three": 1, "128": 1, "bit": 1, "16": 1, "byte": 1, "192": 1, "24": 1, "256": 1, "first": 1, "url_saf": 1, "messag": 1, "gcm": 1, "mode": 1, "12": 1, "fresh": 1, "boolean": 1, "flag": 1, "base64": 1, "serial": 1, "ciphertext": 1, "invalidtag": 1, "wrong": 1, "corrupt": 1, "dotenv_to_t": 1, "dotenv_fil": 1, "drop_exist": 1, "fals": 1, "dot": 1, "transit_decrypt": 1, "appropri": 1, "wa": 1, "index": 1, "search": 1}, "objects": {"vaultapi": [[1, 0, 0, "-", "auth"], [1, 0, 0, "-", "database"], [1, 0, 0, "-", "exceptions"], [1, 0, 0, "-", "main"], [1, 0, 0, "-", "models"], [1, 0, 0, "-", "rate_limit"], [1, 0, 0, "-", "routes"], [1, 0, 0, "-", "squire"], [1, 0, 0, "-", "transit"], [1, 0, 0, "-", "util"]], "vaultapi.auth": [[1, 1, 1, "", "EPOCH"], [1, 1, 1, "", "validate"]], "vaultapi.database": [[1, 1, 1, "", "create_table"], [1, 1, 1, "", "drop_table"], [1, 1, 1, "", "get_secret"], [1, 1, 1, "", "get_table"], [1, 1, 1, "", "put_secret"], [1, 1, 1, "", "remove_secret"], [1, 1, 1, "", "table_exists"]], "vaultapi.exceptions": [[1, 2, 1, "", "APIResponse"]], "vaultapi.main": [[1, 1, 1, "", "enable_cors"], [1, 1, 1, "", "start"]], "vaultapi.models": [[1, 3, 1, "", "Database"], [1, 3, 1, "", "EnvConfig"], [1, 3, 1, "", "RateLimit"], [1, 3, 1, "", "Session"], [1, 1, 1, "", "complexity_checker"], [1, 4, 1, "", "database"], [1, 4, 1, "", "env"]], "vaultapi.models.EnvConfig": [[1, 3, 1, "", "Config"], [1, 4, 1, "", "allowed_ip_range"], [1, 4, 1, "", "allowed_origins"], [1, 4, 1, "", "apikey"], [1, 4, 1, "", "database"], [1, 5, 1, "", "from_env_file"], [1, 4, 1, "", "host"], [1, 4, 1, "", "log_config"], [1, 4, 1, "", "port"], [1, 4, 1, "", "rate_limit"], [1, 4, 1, "", "secret"], [1, 4, 1, "", "transit_key_length"], [1, 5, 1, "", "validate_allowed_ip_range"], [1, 5, 1, "", "validate_allowed_origins"], [1, 5, 1, "", "validate_api_secret"], [1, 5, 1, "", "validate_apikey"], [1, 4, 1, "", "workers"]], "vaultapi.models.EnvConfig.Config": [[1, 4, 1, "", "arbitrary_types_allowed"], [1, 4, 1, "", "extra"], [1, 4, 1, "", "hide_input_in_errors"]], "vaultapi.models.RateLimit": [[1, 4, 1, "", "max_requests"], [1, 4, 1, "", "seconds"]], "vaultapi.models.Session": [[1, 3, 1, "", "Config"], [1, 4, 1, "", "aes_key"], [1, 4, 1, "", "allowed_origins"], [1, 4, 1, "", "fernet"], [1, 4, 1, "", "info"], [1, 4, 1, "", "rps"]], "vaultapi.models.Session.Config": [[1, 4, 1, "", "arbitrary_types_allowed"]], "vaultapi.payload": [[1, 3, 1, "", "DeleteSecret"], [1, 3, 1, "", "PutSecret"]], "vaultapi.payload.DeleteSecret": [[1, 4, 1, "", "key"], [1, 4, 1, "", "table_name"]], "vaultapi.payload.PutSecret": [[1, 4, 1, "", "key"], [1, 4, 1, "", "table_name"], [1, 4, 1, "", "value"]], "vaultapi.rate_limit": [[1, 3, 1, "", "RateLimiter"], [1, 1, 1, "", "_get_identifier"]], "vaultapi.rate_limit.RateLimiter": [[1, 5, 1, "", "init"]], "vaultapi.routes": [[1, 1, 1, "", "create_table"], [1, 1, 1, "", "delete_secret"], [1, 1, 1, "", "docs"], [1, 1, 1, "", "get_all_routes"], [1, 1, 1, "", "get_secret"], [1, 1, 1, "", "get_secrets"], [1, 1, 1, "", "get_table"], [1, 1, 1, "", "health"], [1, 1, 1, "", "put_secret"], [1, 1, 1, "", "put_secrets"], [1, 1, 1, "", "retrieve_secret"], [1, 1, 1, "", "retrieve_secrets"]], "vaultapi.squire": [[1, 1, 1, "", "envfile_loader"], [1, 1, 1, "", "load_env"]], "vaultapi.transit": [[1, 1, 1, "", "decrypt"], [1, 1, 1, "", "encrypt"], [1, 1, 1, "", "string_to_aes_key"]], "vaultapi.util": [[1, 1, 1, "", "dotenv_to_table"], [1, 1, 1, "", "transit_decrypt"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:exception", "3": "py:class", "4": "py:attribute", "5": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "exception", "Python exception"], "3": ["py", "class", "Python class"], "4": ["py", "attribute", "Python attribute"], "5": ["py", "method", "Python method"]}, "titleterms": {"vaultapi": [0, 1], "kick": 0, "off": 0, "environ": 0, "variabl": 0, "code": 0, "standard": 0, "releas": 0, "note": 0, "lint": 0, "pypi": 0, "packag": 0, "docker": 0, "imag": 0, "runbook": 0, "licens": 0, "copyright": 0, "welcom": 1, "": 1, "document": 1, "content": 1, "main": 1, "authent": 1, "databas": 1, "except": 1, "model": 1, "payload": 1, "ratelimit": 1, "api": 1, "rout": 1, "squir": 1, "transmitt": 1, "util": 1, "indic": 1, "tabl": 1}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 56}})
\ No newline at end of file
diff --git a/vaultapi/main.py b/vaultapi/main.py
index 542b5bd..c6cad7f 100644
--- a/vaultapi/main.py
+++ b/vaultapi/main.py
@@ -6,7 +6,7 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
-from . import database, models, routes, squire, version
+from . import database, models, routes, squire, transit, version
LOGGER = logging.getLogger("uvicorn.default")
VaultAPI = FastAPI(
@@ -20,6 +20,9 @@ def __init__(**kwargs) -> None:
"""Instantiates the env, session and database connections."""
models.env = squire.load_env(**kwargs)
models.session.fernet = Fernet(models.env.secret)
+ models.session.aes_key = transit.string_to_aes_key(
+ input_string=models.env.apikey, key_length=models.env.transit_key_length
+ )
models.database = models.Database(models.env.database)
default_allowed = ("0.0.0.0", "127.0.0.1", "localhost")
if models.env.host in default_allowed:
diff --git a/vaultapi/models.py b/vaultapi/models.py
index 87db7d0..07a1486 100644
--- a/vaultapi/models.py
+++ b/vaultapi/models.py
@@ -2,7 +2,7 @@
import re
import socket
import sqlite3
-from typing import Any, Dict, List, Set
+from typing import Any, ByteString, Dict, List, Set
from cryptography.fernet import Fernet
from pydantic import (
@@ -97,6 +97,7 @@ class Session(BaseModel):
"""
fernet: Fernet | None = None
+ aes_key: ByteString | None = None
info: Dict[str, str] = {}
rps: Dict[str, int] = {}
allowed_origins: Set[str] = set()
@@ -116,6 +117,7 @@ class EnvConfig(BaseSettings):
apikey: str
secret: str
+ transit_key_length: PositiveInt = 32
database: FilePath | NewPath | str = Field("secrets.db", pattern=".*.db$")
host: str = socket.gethostbyname("localhost") or "0.0.0.0"
port: PositiveInt = 9010
@@ -138,7 +140,7 @@ class EnvConfig(BaseSettings):
]
@field_validator("allowed_origins", mode="after", check_fields=True)
- def parse_allowed_origins(
+ def validate_allowed_origins(
cls, value: HttpUrl | List[HttpUrl] # noqa: PyMethodParameters
) -> List[HttpUrl]:
"""Validate allowed origins to enable CORS policy."""
@@ -147,7 +149,7 @@ def parse_allowed_origins(
return [value]
@field_validator("allowed_ip_range", mode="after", check_fields=True)
- def parse_allowed_ip_range(
+ def validate_allowed_ip_range(
cls, value: List[str] # noqa: PyMethodParameters
) -> List[str]:
"""Validate allowed IP range to whitelist."""
@@ -165,8 +167,8 @@ def parse_allowed_ip_range(
return value
@field_validator("apikey", mode="after")
- def parse_apikey(cls, value: str) -> str | None: # noqa: PyMethodParameters
- """Parse API key to validate complexity."""
+ def validate_apikey(cls, value: str) -> str | None: # noqa: PyMethodParameters
+ """Validate API key for complexity."""
try:
complexity_checker(value)
except AssertionError as error:
@@ -174,8 +176,8 @@ def parse_apikey(cls, value: str) -> str | None: # noqa: PyMethodParameters
return value
@field_validator("secret", mode="after")
- def parse_api_secret(cls, value: str) -> str: # noqa: PyMethodParameters
- """Parse API secret to Fernet compatible."""
+ def validate_api_secret(cls, value: str) -> str: # noqa: PyMethodParameters
+ """Validate API secret to Fernet compatible."""
try:
Fernet(value)
except ValueError as error:
diff --git a/vaultapi/routes.py b/vaultapi/routes.py
index df60a34..4f10ba6 100644
--- a/vaultapi/routes.py
+++ b/vaultapi/routes.py
@@ -8,7 +8,7 @@
from fastapi.routing import APIRoute
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
-from . import auth, database, exceptions, models, payload, rate_limit
+from . import auth, database, exceptions, models, payload, rate_limit, transit
LOGGER = logging.getLogger("uvicorn.default")
security = HTTPBearer()
@@ -85,7 +85,9 @@ async def get_secret(
if value := await retrieve_secret(key, table_name):
LOGGER.info("Secret value for '%s' was retrieved", key)
decrypted = models.session.fernet.decrypt(value).decode(encoding="UTF-8")
- raise exceptions.APIResponse(status_code=HTTPStatus.OK.real, detail=decrypted)
+ raise exceptions.APIResponse(
+ status_code=HTTPStatus.OK.real, detail=transit.encrypt({key: decrypted})
+ )
LOGGER.info("Secret value for '%s' NOT found in the datastore", key)
raise exceptions.APIResponse(
status_code=HTTPStatus.NOT_FOUND.real, detail=HTTPStatus.NOT_FOUND.phrase
@@ -138,7 +140,9 @@ async def get_secrets(
key: models.session.fernet.decrypt(value).decode(encoding="UTF-8")
for key, value in values.items()
}
- raise exceptions.APIResponse(status_code=code, detail=decrypted)
+ raise exceptions.APIResponse(
+ status_code=code, detail=transit.encrypt(decrypted)
+ )
if keys_ct == 1:
LOGGER.info("Secret value for '%s' NOT found in the datastore", keys[0])
else:
@@ -176,7 +180,9 @@ async def get_table(
key: models.session.fernet.decrypt(value).decode(encoding="UTF-8")
for key, value in table_content.items()
}
- raise exceptions.APIResponse(status_code=HTTPStatus.OK.real, detail=decrypted)
+ raise exceptions.APIResponse(
+ status_code=HTTPStatus.OK.real, detail=transit.encrypt(decrypted)
+ )
async def put_secret(
diff --git a/vaultapi/transit.py b/vaultapi/transit.py
new file mode 100644
index 0000000..8514b29
--- /dev/null
+++ b/vaultapi/transit.py
@@ -0,0 +1,80 @@
+"""Module that performs transit encryption/decryption.
+
+This allows the server to securely transmit the retrieved secret to be decrypted at the client side using the API key.
+"""
+
+import base64
+import hashlib
+import json
+import secrets
+from typing import Any, ByteString, Dict
+
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+
+from . import models
+
+
+def string_to_aes_key(input_string: str, key_length: int) -> ByteString:
+ """Hashes the string.
+
+ Args:
+ input_string: String for which an AES hash has to be generated.
+ key_length: AES key size used during encryption.
+
+ See Also:
+ AES supports three key lengths:
+ - 128 bits (16 bytes)
+ - 192 bits (24 bytes)
+ - 256 bits (32 bytes)
+
+ Returns:
+ str:
+ Return the first 16 bytes for the AES key
+ """
+ hash_object = hashlib.sha256(input_string.encode())
+ return hash_object.digest()[:key_length]
+
+
+def encrypt(payload: Dict[str, Any], url_safe: bool = True) -> ByteString | str:
+ """Encrypt a message using GCM mode with 12 fresh bytes.
+
+ Args:
+ payload: Payload to be encrypted.
+ url_safe: Boolean flag to perform base64 encoding to perform JSON serialization.
+
+ Returns:
+ ByteString | str:
+ Returns the ciphertext as a string or bytes based on the ``url_safe`` flag.
+ """
+ nonce = secrets.token_bytes(12)
+ encoded = json.dumps(payload).encode()
+ # todo: instead of loading aes_key in memory,
+ # generate it on-demand and append a UTC time value that remains constant for at least 60s (probably env var)
+ ciphertext = nonce + AESGCM(models.session.aes_key).encrypt(nonce, encoded, b"")
+ if url_safe:
+ return base64.b64encode(ciphertext).decode("utf-8")
+ return ciphertext
+
+
+def decrypt(ciphertext: ByteString | str) -> Dict[str, Any]:
+ """Decrypt the ciphertext.
+
+ Raises:
+ Raises ``InvalidTag`` if using wrong key or corrupted ciphertext.
+
+ Returns:
+ Dict[str, Any]:
+ Returns the JSON serialized decrypted payload.
+ """
+ if isinstance(ciphertext, str):
+ ciphertext = base64.b64decode(ciphertext)
+ decrypted = AESGCM(models.session.aes_key).decrypt(
+ ciphertext[:12], ciphertext[12:], b""
+ )
+ return json.loads(decrypted)
+
+
+if __name__ == "__main__":
+ encrypted = encrypt({"key": "value"})
+ b64_encoded = base64.b64encode(encrypted).decode("utf-8")
+ print(decrypt(b64_encoded))
diff --git a/vaultapi/util.py b/vaultapi/util.py
index 1f039e6..d2fc76b 100644
--- a/vaultapi/util.py
+++ b/vaultapi/util.py
@@ -1,7 +1,12 @@
+import base64
+import hashlib
import importlib
+import json
import logging
import sqlite3
+from typing import Any, ByteString, Dict
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from dotenv import dotenv_values
from . import database, main, models
@@ -52,3 +57,25 @@ def dotenv_to_table(
encrypted = models.session.fernet.encrypt(value.encode(encoding="UTF-8"))
database.put_secret(key, encrypted, table_name)
LOGGER.info("%d secrets have been stored to the database.", len(env_vars))
+
+
+def transit_decrypt(
+ apikey: str, ciphertext: str | ByteString, key_length: int = 32
+) -> Dict[str, Any]:
+ """Decrypts the ciphertext into an appropriate payload.
+
+ Args:
+ apikey: API key that was used to encrypt the payload.
+ ciphertext: Encrypted ciphertext.
+ key_length: AES key size used during encryption.
+
+ Returns:
+ Dict[str, Any]:
+ Returns the decrypted payload.
+ """
+ hash_object = hashlib.sha256(apikey.encode())
+ aes_key = hash_object.digest()[:key_length]
+ if isinstance(ciphertext, str):
+ ciphertext = base64.b64decode(ciphertext)
+ decrypted = AESGCM(aes_key).decrypt(ciphertext[:12], ciphertext[12:], b"")
+ return json.loads(decrypted)
|