diff --git a/docs/genindex.html b/docs/genindex.html index a97f144..7b7a176 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -42,7 +42,8 @@

Navigation

Index

- A + _ + | A | C | D | E @@ -61,6 +62,14 @@

Index

| W
+

_

+ + +
+

A

    @@ -131,8 +140,6 @@

    E

    • enable_cors() (in module vaultapi.main) -
    • -
    • endpoints (vaultapi.models.EnvConfig attribute)
    • env (in module vaultapi.models)
    • @@ -271,11 +278,11 @@

      M

      P

        +
      • parse_allowed_origins() (vaultapi.models.EnvConfig class method) +
      • parse_api_secret() (vaultapi.models.EnvConfig class method)
      • parse_apikey() (vaultapi.models.EnvConfig class method) -
      • -
      • parse_endpoints() (vaultapi.models.EnvConfig class method)
      • port (vaultapi.models.EnvConfig attribute)
      • diff --git a/docs/index.html b/docs/index.html index e2528f3..0fd4cbe 100644 --- a/docs/index.html +++ b/docs/index.html @@ -323,11 +323,6 @@

        Modelsdatabase: Union[Path, Path, str]
        -
        -
        -endpoints: Union[Url, List[Url]]
        -
        -
        host: str
        @@ -350,7 +345,7 @@

        Models
        -allowed_origins: List[str]
        +allowed_origins: Union[Url, List[Url]]

        @@ -359,9 +354,9 @@

        Models

        -
        -classmethod parse_endpoints(value: Union[Url, List[Url]]) List[Url]
        -

        Validate endpoints to enable CORS policy.

        +
        +classmethod parse_allowed_origins(value: Union[Url, List[Url]]) List[Url]
        +

        Validate allowed origins to enable CORS policy.

        @@ -372,8 +367,8 @@

        Models
        -classmethod parse_api_secret(value: str | None) str | None
        -

        Parse API secret to validate complexity.

        +classmethod parse_api_secret(value: str) str +

        Parse API secret to Fernet compatible.

        @@ -419,16 +414,8 @@

        Models
        -vaultapi.models.complexity_checker(secret: str, simple: bool = False) None
        +vaultapi.models.complexity_checker(secret: str) None

        Verifies the strength of a secret.

        -
        -
        Parameters:
        -
          -
        • secret – Value of the secret.

        • -
        • simple – Boolean flag to increase complexity.

        • -
        -
        -

        See also

        A secret is considered strong if it at least has:

        @@ -526,10 +513,16 @@

        Payload

        RateLimit

        +
        +
        +vaultapi.rate_limit._get_identifier(request: Request) str
        +

        Generate a unique identifier for the request.

        +
        +
        class vaultapi.rate_limit.RateLimiter(rps: RateLimit)
        -

        Object that implements the RateLimiter functionality.

        +

        Rate limiter for incoming requests.

        >>> RateLimiter
         
        diff --git a/docs/objects.inv b/docs/objects.inv index ed7e6a5..67bcf83 100644 Binary files a/docs/objects.inv and b/docs/objects.inv differ diff --git a/docs/searchindex.js b/docs/searchindex.js index 2a8d2b9..6de3193 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, "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, "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, "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, "endpoint": 1, "url": 1, "classmethod": 1, "parse_endpoint": 1, "parse_apikei": 1, "pars": 1, "complex": 1, "parse_api_secret": 1, "from_env_fil": 1, "instanc": 1, "extra": 1, "ignor": 1, "hide_input_in_error": 1, "complexity_check": 1, "simpl": 1, "fals": 1, "verifi": 1, "strength": 1, "boolean": 1, "flag": 1, "increas": 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, "implement": 1, "init": 1, "exce": 1, "given": 1, "identifi": 1, "The": 1, "incom": 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, "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, "dot": 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_origins"], [1, 4, 1, "", "apikey"], [1, 4, 1, "", "database"], [1, 4, 1, "", "endpoints"], [1, 5, 1, "", "from_env_file"], [1, 4, 1, "", "host"], [1, 4, 1, "", "log_config"], [1, 5, 1, "", "parse_api_secret"], [1, 5, 1, "", "parse_apikey"], [1, 5, 1, "", "parse_endpoints"], [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"]], "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, "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, "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, "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, "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, "classmethod": 1, "parse_allowed_origin": 1, "origin": 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_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_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, "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 diff --git a/vaultapi/auth.py b/vaultapi/auth.py index 0e07a46..8d2144c 100644 --- a/vaultapi/auth.py +++ b/vaultapi/auth.py @@ -25,6 +25,15 @@ async def validate(request: Request, apikey: HTTPAuthorizationCredentials) -> No - 401: If authorization is invalid. - 403: If host address is forbidden. """ + if request.client.host not in models.session.allowed_origins: + LOGGER.info( + "Host: %s has been blocked since it is not added to allowed list", + request.client.host, + ) + LOGGER.info(models.session.allowed_origins) + raise exceptions.APIResponse( + status_code=HTTPStatus.FORBIDDEN.real, detail=HTTPStatus.FORBIDDEN.phrase + ) if apikey.credentials.startswith("\\"): auth = bytes(apikey.credentials, "utf-8").decode(encoding="unicode_escape") else: diff --git a/vaultapi/main.py b/vaultapi/main.py index 13bd4fd..e4cc8ab 100644 --- a/vaultapi/main.py +++ b/vaultapi/main.py @@ -21,6 +21,14 @@ def __init__(**kwargs) -> None: models.env = squire.load_env(**kwargs) models.session.fernet = Fernet(models.env.secret) 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: + models.session.allowed_origins.update(default_allowed) + else: + models.session.allowed_origins.add(models.env.host) + for allowed in models.env.allowed_origins: + models.session.allowed_origins.add(allowed.host) + LOGGER.info("Allowed origins: %s", models.session.allowed_origins) def enable_cors() -> None: @@ -30,7 +38,7 @@ def enable_cors() -> None: "http://localhost.com", "https://localhost.com", ] - for website in models.env.endpoints: + for website in models.env.allowed_origins: origins.append(f"http://{website.host}") # noqa: HttpUrlsUsage origins.append(f"https://{website.host}") VaultAPI.add_middleware( diff --git a/vaultapi/models.py b/vaultapi/models.py index 8ffa3f6..e3b54f6 100644 --- a/vaultapi/models.py +++ b/vaultapi/models.py @@ -17,13 +17,9 @@ from pydantic_settings import BaseSettings -def complexity_checker(secret: str, simple: bool = False) -> None: +def complexity_checker(secret: str) -> None: """Verifies the strength of a secret. - Args: - secret: Value of the secret. - simple: Boolean flag to increase complexity. - See Also: A secret is considered strong if it at least has: @@ -36,19 +32,14 @@ def complexity_checker(secret: str, simple: bool = False) -> None: Raises: AssertionError: When at least 1 of the above conditions fail to match. """ - char_limit = 8 if simple else 32 - # calculates the length assert ( - len(secret) >= char_limit - ), f"Minimum secret length is {char_limit}, received {len(secret)}" + len(secret) >= 32 + ), f"secret length must be at least 32, received {len(secret)}" # searches for digits assert re.search(r"\d", secret), "secret must include an integer" - if simple: - return - # searches for uppercase assert re.search( r"[A-Z]", secret @@ -61,7 +52,7 @@ def complexity_checker(secret: str, simple: bool = False) -> None: # searches for symbols assert re.search( - r"[ !#$%&'()*+,-./[\\\]^_`{|}~" + r'"]', secret + r"[ !@#$%^&*()_='+,-./[\\\]`{|}~" + r'"]', secret ), "secret must contain at least one special character" @@ -126,19 +117,28 @@ class EnvConfig(BaseSettings): apikey: str secret: str database: FilePath | NewPath | str = Field("secrets.db", pattern=".*.db$") - endpoints: HttpUrl | List[HttpUrl] = [] host: str = socket.gethostbyname("localhost") or "0.0.0.0" port: PositiveInt = 8080 workers: PositiveInt = 1 log_config: FilePath | Dict[str, Any] | None = None - allowed_origins: List[str] = [] - rate_limit: RateLimit | List[RateLimit] = [] - - @field_validator("endpoints", mode="after", check_fields=True) - def parse_endpoints( + allowed_origins: HttpUrl | List[HttpUrl] = [] + # This is a base rate limit configuration + rate_limit: RateLimit | List[RateLimit] = [ + { + "max_requests": 5, + "seconds": 2, + }, # Burst limit: Prevents excessive load on the server + { + "max_requests": 10, + "seconds": 30, + }, # Sustained limit: Prevents too many trial and errors + ] + + @field_validator("allowed_origins", mode="after", check_fields=True) + def parse_allowed_origins( cls, value: HttpUrl | List[HttpUrl] # noqa: PyMethodParameters ) -> List[HttpUrl]: - """Validate endpoints to enable CORS policy.""" + """Validate allowed origins to enable CORS policy.""" if isinstance(value, list): return value return [value] @@ -148,22 +148,20 @@ def parse_apikey(cls, value: str | None) -> str | None: # noqa: PyMethodParamet """Parse API key to validate complexity.""" if value: try: - complexity_checker(value, True) + complexity_checker(value) except AssertionError as error: raise ValueError(error.__str__()) return value @field_validator("secret", mode="after") - def parse_api_secret( - cls, value: str | None # noqa: PyMethodParameters - ) -> str | None: - """Parse API secret to validate complexity.""" - if value: - try: - complexity_checker(value) - except AssertionError as error: - raise ValueError(error.__str__()) - return value + def parse_api_secret(cls, value: str) -> str: # noqa: PyMethodParameters + """Parse API secret to Fernet compatible.""" + try: + Fernet(value) + except ValueError as error: + exc = f"{error}\n\tConsider using 'vaultapi keygen' command to generate a valid secret." + raise ValueError(exc) + return value @classmethod def from_env_file(cls, env_file: pathlib.Path) -> "EnvConfig": diff --git a/vaultapi/rate_limit.py b/vaultapi/rate_limit.py index d7355a7..e3b68dd 100644 --- a/vaultapi/rate_limit.py +++ b/vaultapi/rate_limit.py @@ -1,14 +1,23 @@ import math import time +from collections import defaultdict from http import HTTPStatus +from threading import Lock from fastapi import HTTPException, Request from . import models +def _get_identifier(request: Request) -> str: + """Generate a unique identifier for the request.""" + if forwarded := request.headers.get("x-forwarded-for"): + return f"{forwarded.split(',')[0]}:{request.url.path}" + return f"{request.client.host}:{request.url.path}" + + class RateLimiter: - """Object that implements the ``RateLimiter`` functionality. + """Rate limiter for incoming requests. >>> RateLimiter @@ -27,13 +36,8 @@ def __init__(self, rps: models.RateLimit): """ self.max_requests = rps.max_requests self.seconds = rps.seconds - self.start_time = time.time() - self.exception = HTTPException( - status_code=HTTPStatus.TOO_MANY_REQUESTS.value, - detail=HTTPStatus.TOO_MANY_REQUESTS.phrase, - # reset headers, which will invalidate auth token - headers={"Retry-After": str(math.ceil(self.seconds))}, - ) + self.locks = defaultdict(Lock) # For thread-safe access + self.requests = defaultdict(list) def init(self, request: Request) -> None: """Checks if the number of calls exceeds the rate limit for the given identifier. @@ -44,23 +48,21 @@ def init(self, request: Request) -> None: Raises: 429: Too many requests. """ - if forwarded := request.headers.get("x-forwarded-for"): - identifier = forwarded.split(",")[0] - else: - identifier = request.client.host - identifier += ":" + request.url.path - + identifier = _get_identifier(request) current_time = time.time() - # Reset if the time window has passed - if current_time - self.start_time > self.seconds: - models.session.rps[identifier] = 1 - self.start_time = current_time - - if models.session.rps.get(identifier): - if models.session.rps[identifier] >= self.max_requests: - raise self.exception - else: - models.session.rps[identifier] += 1 - else: - models.session.rps[identifier] = 1 + with self.locks[identifier]: + # Clean up expired timestamps + self.requests[identifier] = [ + timestamp + for timestamp in self.requests[identifier] + if current_time - timestamp < self.seconds + ] + + if len(self.requests[identifier]) >= self.max_requests: + raise HTTPException( + status_code=HTTPStatus.TOO_MANY_REQUESTS.value, + detail=HTTPStatus.TOO_MANY_REQUESTS.phrase, + headers={"Retry-After": str(math.ceil(self.seconds))}, + ) + self.requests[identifier].append(current_time)