From 8d00d117a6012422c8758e15ac669149af3d00ba Mon Sep 17 00:00:00 2001 From: Vignesh Rao Date: Tue, 17 Sep 2024 17:24:40 -0500 Subject: [PATCH] Create a util script to convert `.env` to datastore Create an endpoint to put multiple secrets at a time --- .gitignore | 3 +- doc_gen/index.rst | 7 +- docs/_sources/index.rst.txt | 7 +- docs/genindex.html | 49 +++++++++++-- docs/index.html | 133 +++++++++++++++++++++++++++++++++++- docs/objects.inv | Bin 844 -> 882 bytes docs/py-modindex.html | 7 +- docs/searchindex.js | 2 +- vaultapi/database.py | 44 ++++++++++++ vaultapi/main.py | 20 +++--- vaultapi/models.py | 24 ++----- vaultapi/payload.py | 15 +++- vaultapi/routes.py | 29 +++++++- vaultapi/util.py | 54 +++++++++++++++ 14 files changed, 350 insertions(+), 44 deletions(-) create mode 100644 vaultapi/util.py diff --git a/.gitignore b/.gitignore index 5a1ca2c..72d5c48 100644 --- a/.gitignore +++ b/.gitignore @@ -10,10 +10,11 @@ PyNinja.egg-info/ doc_gen/_* -temp.py +temp*.py logging.ini *.log *.db +*.env discard/ diff --git a/doc_gen/index.rst b/doc_gen/index.rst index 8772128..bf91e65 100644 --- a/doc_gen/index.rst +++ b/doc_gen/index.rst @@ -47,7 +47,7 @@ Models ==== -.. automodule:: vaultapi.models.EnvConfig(BaseSettings) +.. automodule:: vaultapi.models :exclude-members: RateLimit, Session, EnvConfig Payload @@ -76,6 +76,11 @@ Squire .. automodule:: vaultapi.squire +Util +==== + +.. automodule:: vaultapi.util + Indices and tables ================== diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index 8772128..bf91e65 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -47,7 +47,7 @@ Models ==== -.. automodule:: vaultapi.models.EnvConfig(BaseSettings) +.. automodule:: vaultapi.models :exclude-members: RateLimit, Session, EnvConfig Payload @@ -76,6 +76,11 @@ Squire .. automodule:: vaultapi.squire +Util +==== + +.. automodule:: vaultapi.util + Indices and tables ================== diff --git a/docs/genindex.html b/docs/genindex.html index eb3508e..a97f144 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -88,16 +88,30 @@

A

C

+

D

@@ -105,6 +119,10 @@

D

  • DeleteSecret (class in vaultapi.payload)
  • docs() (in module vaultapi.routes) +
  • +
  • dotenv_to_table() (in module vaultapi.util) +
  • +
  • drop_table() (in module vaultapi.database)
  • @@ -115,6 +133,8 @@

    E

  • enable_cors() (in module vaultapi.main)
  • endpoints (vaultapi.models.EnvConfig attribute) +
  • +
  • env (in module vaultapi.models)
  • EnvConfig (class in vaultapi.models)
  • @@ -234,13 +254,15 @@

    M

  • vaultapi.main
  • -
  • vaultapi.models.EnvConfig(BaseSettings) +
  • vaultapi.models
  • vaultapi.rate_limit
  • vaultapi.routes
  • vaultapi.squire +
  • +
  • vaultapi.util
  • @@ -255,16 +277,18 @@

    P

  • parse_endpoints() (vaultapi.models.EnvConfig class method)
  • - - + @@ -312,6 +336,10 @@

    S

    T

    + diff --git a/docs/index.html b/docs/index.html index a07220a..e2528f3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -123,6 +123,31 @@

    Welcome to VaultAPI’s documentation!

    Database

    +
    +
    +vaultapi.database.table_exists(table_name: str) bool
    +

    Function to check if a table exists in the database.

    +
    +
    Parameters:
    +

    table_name – Name of the table to check.

    +
    +
    +
    + +
    +
    +vaultapi.database.create_table(table_name: str, columns: Union[List[str], Tuple[str]]) None
    +

    Creates the table with the required columns.

    +
    +
    Parameters:
    +
      +
    • table_name – Name of the table that has to be created.

    • +
    • columns – List of columns that has to be created.

    • +
    +
    +
    +
    +
    vaultapi.database.get_secret(key: str, table_name: str) str | None
    @@ -189,6 +214,17 @@

    Welcome to VaultAPI’s documentation! +
    +vaultapi.database.drop_table(table_name: str) None
    +

    Function to drop a table from the database.

    +
    +
    Parameters:
    +

    table_name – Name of the table to be dropped.

    +
    +
    +

    +

    Exceptions

    @@ -381,10 +417,65 @@

    Models
    -

    Object to load environment variables.

    -
    >>> EnvConfig
    +
    +
    +vaultapi.models.complexity_checker(secret: str, simple: bool = False) 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:

    +
      +
    • 32 characters

    • +
    • 1 digit

    • +
    • 1 symbol

    • +
    • 1 uppercase letter

    • +
    • 1 lowercase letter

    • +
    +
    +
    +
    Raises:
    +

    AssertionError – When at least 1 of the above conditions fail to match.

    +
    +
    +
    + +
    +
    +class vaultapi.models.Database(filepath: Union[Path, str], timeout: int = 10)
    +

    Creates a connection and instantiates the cursor.

    +
    >>> Database
     
    +
    +
    Parameters:
    +
      +
    • filepath – Name of the database file.

    • +
    • timeout – Timeout for the connection to database.

    • +
    +
    +
    +
    + +
    +
    +vaultapi.models.database
    +

    alias of Database

    +
    + +
    +
    +vaultapi.models.env
    +

    alias of EnvConfig

    +
    +

    Payload

    @@ -412,7 +503,7 @@

    Payload class vaultapi.payload.PutSecret(BaseModel)

    Payload for put-secret API call.

    -
    >>> DeleteSecret
    +
    >>> PutSecret
     
    @@ -571,6 +662,23 @@

    Payload +
    +async vaultapi.routes.put_secrets(request: Request, data: PutSecrets, apikey: HTTPAuthorizationCredentials = Depends(HTTPBearer))
    +

    API function to add multiple secrets to a table in the database.

    +

    Args:

    +
    +

    request: Reference to the FastAPI request object. +data: Payload with key, value, and table_name as body. +apikey: API Key to authenticate the request.

    +
    +

    Raises:

    +
    +

    APIResponse: +Raises the HTTPStatus object with a status code and detail as response.

    +
    +

    +
    async vaultapi.routes.delete_secret(request: Request, data: DeleteSecret, apikey: HTTPAuthorizationCredentials = Depends(HTTPBearer))
    @@ -685,6 +793,24 @@

    Payload +

    Util

    +
    +
    +vaultapi.util.dotenv_to_table(table_name: str, dotenv_file: str, drop_existing: bool = False, **kwargs) None
    +

    Store all the env vars from a .env file into the database.

    +
    +
    Parameters:
    +
      +
    • table_name – Name of the table to store secrets.

    • +
    • dotenv_file – Dot env filename.

    • +
    • drop_existing – Boolean flag to drop existing table.

    • +
    +
    +
    +
    +

    Indices and tables

    @@ -715,6 +841,7 @@

    Table of Contents

  • RateLimit
  • API Routes
  • Squire
  • +
  • Util
  • Indices and tables
  • diff --git a/docs/objects.inv b/docs/objects.inv index 296c2c7cd9c9d2ffdd42f5640aea2d20306bcfb1..ed7e6a5ca4c74696a4d94715ab618cd677e69454 100644 GIT binary patch delta 776 zcmV+j1NZ#Q2J!}wcYl~mZrd;rhW9>&igs(E*=83c1{$D2jRa_y1TBp%A{5DxBgNYF z8ogdmQc{#GIWcOI&dw(1`)7uaA*q%&9I0YCZLrEzcU{Y};gEgJm?&YNS6K7UzHRv> zTQ1tUeZE@%xc%m+Z6gX~Qe;d@4B%pldF<7j;Yj~WM0tpbPO7$puSv?$8wxC7y+8JRcwlWdbDmLOm z){5IJ5q6}gps-6e>Nb#CxNV&cB^2$rq-A~w91xxkyYk#>ImP7>XVgjFUm*ppAy#JH zV0`QcD1RmJ3I2en-9xAe#*fQy=yh?TrgG%hEr!p=Y~Z9Y*~TTi4s*#@%g zmsQ+LW<+IS;t2Orvz3iZdB(9RQBGWf!-N5?fO7vuAe@PQh`!?@bF+kI(5B7%8#vUp zd1;uA`Xb>8SESutn7B$1d@c;EW5`hD1E>ZWkAI2K+1q`9?!JyPWVt-9ZtfmdnZfef z?w@RVmHC$5BKzrt?OT1Z1JQ%MA*UPQ&U9?`#`o^$Vlo@_W){DI+k+J{ln3R%zyFxb zS~fKZq%Ou!3!B*qXR2F{9bWCh4~lm?>pg?5J6rX{F>QeAqB;hBn`fSU|C;V#@efJm G_W`Ug&w&yE delta 737 zcmV<70v`SH2FwPKcYl}7j@uv*$M1OxSM8xa1a+@_+wMl{N?kQdr1pr2J+{$e@B;S6 zx4uSSuTN4icI>EW0%UI%%8T-i<9k zB+ErRw@0h>_xrDw${U_)DtK};=l%s+!e~Y`crwP6?1+Mpz<)*t6`-+#RDurl)|2Lw zg@%dhCf22dO0*CL!#;)D+NhBFOcc6XKW;$P=CjZAFmuLdMJNyPX6+GhTTGk^sXSBb zF{O$0t%dOoOraHT?*-3kVX>4Er2?3bU(8BuL2Jqj_31*E|5aJeRnECX@sSQFB63Hy zB(lT0t3hGHn16VM%t{iiCEYdVsD;bI-iTf+Qql}C<@QKWjzP*{^`kV0#ITUGp!^D* znm$7pjnDg0lmPIo7L@Dw400(d47|lTb^WOVU5YG3q7+I;`)8ss2YKAMQ4%$g3b2`) zwY%^v4l2Wf#wr{wNrZ@y@x_$pSBM#{XuQN!!YKSz$bV-DKA0O5_qQ=@&*pVtOmp93 z2`>#O9qriWEqE5nZ$T;hBk>P=Ai|sVG5AKUdKA5`AC4+HNFKa*Mws$kM540xj<_Zr z6C^_qz(AYd>bTi2Ey5`d1+@N?e19jzoKz4hvu<>F><1_&@Ckl{sO`PI@y3t4n(s|< zqNY<0(toF3r%AIxso$?f3X@OJ9oiAdl*YOac`6KK)^Dq@>;8y}RD}`tIb#|Ip7IQ1 zW1^h62!{~`ErBrifc0mBUuy$r07;>Rw}5@Cqh1;+gWkc8FgMQD-W0Jt(C#>SdqbA7 zcg^$EW_9=Qv`UoDZq43ImN$tT=`E5UR?|E*7g`1I9_$Sn*#WbrPython Module Index

    @@ -98,6 +98,11 @@

    Python Module Index

    + + +
    • - vaultapi.models.EnvConfig(BaseSettings) + vaultapi.models
    • @@ -385,6 +413,13 @@

      V

    • +
    • + vaultapi.util + +
        - vaultapi.models.EnvConfig(BaseSettings) + vaultapi.models
        vaultapi.squire
        + vaultapi.util +
    diff --git a/docs/searchindex.js b/docs/searchindex.js index 8acaf53..2a8d2b9 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, "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, "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, "get_secret": 1, "str": 1, "table_nam": 1, "name": 1, "where": 1, "return": 1, "type": 1, "get_tabl": 1, "tupl": 1, "pair": 1, "particular": 1, "ar": 1, "put_secret": 1, "add": 1, "remove_secret": 1, "remov": 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, "union": 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, "creat": 1, "instanc": 1, "extra": 1, "ignor": 1, "hide_input_in_error": 1, "deletesecret": 1, "delet": 1, "call": 1, "putsecret": 1, "put": 1, "implement": 1, "init": 1, "check": 1, "exce": 1, "given": 1, "identifi": 1, "The": 1, "incom": 1, "429": 1, "too": 1, "mani": 1, "retrieve_secret": 1, "exist": 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, "create_t": 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, "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, "-", "rate_limit"], [1, 0, 0, "-", "routes"], [1, 0, 0, "-", "squire"]], "vaultapi.auth": [[1, 1, 1, "", "EPOCH"], [1, 1, 1, "", "validate"]], "vaultapi.database": [[1, 1, 1, "", "get_secret"], [1, 1, 1, "", "get_table"], [1, 1, 1, "", "put_secret"], [1, 1, 1, "", "remove_secret"]], "vaultapi.exceptions": [[1, 2, 1, "", "APIResponse"]], "vaultapi.main": [[1, 1, 1, "", "enable_cors"], [1, 1, 1, "", "start"]], "vaultapi.models": [[1, 3, 1, "", "EnvConfig"], [1, 0, 0, "module-vaultapi.models.EnvConfig-BaseSettings", "EnvConfig(BaseSettings)"], [1, 3, 1, "", "RateLimit"], [1, 3, 1, "", "Session"]], "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, "", "retrieve_secret"], [1, 1, 1, "", "retrieve_secrets"]], "vaultapi.squire": [[1, 1, 1, "", "envfile_loader"], [1, 1, 1, "", "load_env"]]}, "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, "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, "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 diff --git a/vaultapi/database.py b/vaultapi/database.py index 84f0b67..05ac841 100644 --- a/vaultapi/database.py +++ b/vaultapi/database.py @@ -3,6 +3,38 @@ from . import models +def table_exists(table_name: str) -> bool: + """Function to check if a table exists in the database. + + Args: + table_name: Name of the table to check. + """ + with models.database.connection: + cursor = models.database.connection.cursor() + cursor.execute( + "SELECT name FROM sqlite_master WHERE type='table' AND name=?", + (table_name,), + ) + result = cursor.fetchone() + if result: + return True + + +def create_table(table_name: str, columns: List[str] | Tuple[str]) -> None: + """Creates the table with the required columns. + + Args: + table_name: Name of the table that has to be created. + columns: List of columns that has to be created. + """ + with models.database.connection: + cursor = models.database.connection.cursor() + # Use f-string or %s as table names cannot be parametrized + cursor.execute( + f"CREATE TABLE IF NOT EXISTS {table_name!r} ({', '.join(columns)})" + ) + + def get_secret(key: str, table_name: str) -> str | None: """Function to retrieve secret from database. @@ -67,3 +99,15 @@ def remove_secret(key: str, table_name: str) -> None: cursor = models.database.connection.cursor() cursor.execute(f'DELETE FROM "{table_name}" WHERE key=(?)', (key,)) models.database.connection.commit() + + +def drop_table(table_name: str) -> None: + """Function to drop a table from the database. + + Args: + table_name: Name of the table to be dropped. + """ + with models.database.connection: + cursor = models.database.connection.cursor() + cursor.execute(f'DROP TABLE IF EXISTS "{table_name}"') + models.database.connection.commit() diff --git a/vaultapi/main.py b/vaultapi/main.py index 9d6719a..13bd4fd 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 models, routes, squire, version +from . import database, models, routes, squire, version LOGGER = logging.getLogger("uvicorn.default") VaultAPI = FastAPI( @@ -16,6 +16,13 @@ ) +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.database = models.Database(models.env.database) + + def enable_cors() -> None: """Enables CORS policy.""" LOGGER.info("Setting CORS policy") @@ -24,9 +31,8 @@ def enable_cors() -> None: "https://localhost.com", ] for website in models.env.endpoints: - origins.extend( - [f"http://{website.host}", f"https://{website.host}"] - ) # noqa: HttpUrlsUsage + origins.append(f"http://{website.host}") # noqa: HttpUrlsUsage + origins.append(f"https://{website.host}") VaultAPI.add_middleware( CORSMiddleware, # noqa: PyTypeChecker allow_origins=origins, @@ -55,10 +61,8 @@ def start(**kwargs) -> None: rate_limit: List of dictionaries with ``max_requests`` and ``seconds`` to apply as rate limit. log_config: Logging configuration as a dict or a FilePath. Supports .yaml/.yml, .json or .ini formats. """ - models.env = squire.load_env(**kwargs) - models.session.fernet = Fernet(models.env.secret) - models.database = models.Database(models.env.database) - models.database.create_table("default", ["key", "value"]) + __init__(**kwargs) + database.create_table("default", ["key", "value"]) module_name = pathlib.Path(__file__) enable_cors() VaultAPI.routes.extend(routes.get_all_routes()) diff --git a/vaultapi/models.py b/vaultapi/models.py index d784b98..8ffa3f6 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, Tuple +from typing import Any, Dict, List, Set from cryptography.fernet import Fernet from pydantic import ( @@ -83,20 +83,6 @@ def __init__(self, filepath: FilePath | str, timeout: int = 10): database=filepath, check_same_thread=False, timeout=timeout ) - def create_table(self, table_name: str, columns: List[str] | Tuple[str]) -> None: - """Creates the table with the required columns. - - Args: - table_name: Name of the table that has to be created. - columns: List of columns that has to be created. - """ - with self.connection: - cursor = self.connection.cursor() - # Use f-string or %s as table names cannot be parametrized - cursor.execute( - f"CREATE TABLE IF NOT EXISTS {table_name!r} ({', '.join(columns)})" - ) - database: Database = Database # noqa: PyTypeChecker @@ -150,8 +136,8 @@ class EnvConfig(BaseSettings): @field_validator("endpoints", mode="after", check_fields=True) def parse_endpoints( - cls, value: HttpUrl | List[HttpUrl] - ) -> List[HttpUrl]: # noqa: PyMethodParameters + cls, value: HttpUrl | List[HttpUrl] # noqa: PyMethodParameters + ) -> List[HttpUrl]: """Validate endpoints to enable CORS policy.""" if isinstance(value, list): return value @@ -169,8 +155,8 @@ def parse_apikey(cls, value: str | None) -> str | None: # noqa: PyMethodParamet @field_validator("secret", mode="after") def parse_api_secret( - cls, value: str | None - ) -> str | None: # noqa: PyMethodParameters + cls, value: str | None # noqa: PyMethodParameters + ) -> str | None: """Parse API secret to validate complexity.""" if value: try: diff --git a/vaultapi/payload.py b/vaultapi/payload.py index a233a51..c0945f6 100644 --- a/vaultapi/payload.py +++ b/vaultapi/payload.py @@ -1,3 +1,5 @@ +from typing import Dict + from pydantic import BaseModel @@ -15,10 +17,21 @@ class DeleteSecret(BaseModel): class PutSecret(BaseModel): """Payload for put-secret API call. - >>> DeleteSecret + >>> PutSecret """ key: str value: str table_name: str = "default" + + +class PutSecrets(BaseModel): + """Payload for put-secrets API call. + + >>> PutSecret + + """ + + secrets: Dict[str, str] + table_name: str = "default" diff --git a/vaultapi/routes.py b/vaultapi/routes.py index 846fadf..df60a34 100644 --- a/vaultapi/routes.py +++ b/vaultapi/routes.py @@ -213,6 +213,33 @@ async def put_secret( ) +async def put_secrets( + request: Request, + data: payload.PutSecrets, + apikey: HTTPAuthorizationCredentials = Depends(security), +): + """**API function to add multiple secrets to a table in the database.** + + **Args:** + + request: Reference to the FastAPI request object. + data: Payload with ``key``, ``value``, and ``table_name`` as body. + apikey: API Key to authenticate the request. + + **Raises:** + + APIResponse: + Raises the HTTPStatus object with a status code and detail as response. + """ + await auth.validate(request, apikey) + for key, value in data.secrets.items(): + encrypted = models.session.fernet.encrypt(value.encode(encoding="UTF-8")) + database.put_secret(key=key, value=encrypted, table_name=data.table_name) + raise exceptions.APIResponse( + status_code=HTTPStatus.OK.real, detail=HTTPStatus.OK.phrase + ) + + async def delete_secret( request: Request, data: payload.DeleteSecret, @@ -265,7 +292,7 @@ async def create_table( """ await auth.validate(request, apikey) try: - models.database.create_table(table_name, ["key", "value"]) + database.create_table(table_name, ["key", "value"]) except sqlite3.OperationalError as error: LOGGER.error(error) raise exceptions.APIResponse( diff --git a/vaultapi/util.py b/vaultapi/util.py new file mode 100644 index 0000000..1f039e6 --- /dev/null +++ b/vaultapi/util.py @@ -0,0 +1,54 @@ +import importlib +import logging +import sqlite3 + +from dotenv import dotenv_values + +from . import database, main, models + +importlib.reload(logging) +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +HANDLER = logging.StreamHandler() +DEFAULT_FORMATTER = logging.Formatter( + datefmt="%b-%d-%Y %I:%M:%S %p", + fmt="%(asctime)s - %(levelname)s - [%(module)s:%(lineno)d] - %(funcName)s - %(message)s", +) +HANDLER.setFormatter(DEFAULT_FORMATTER) +LOGGER.addHandler(HANDLER) + + +def dotenv_to_table( + table_name: str, dotenv_file: str, drop_existing: bool = False, **kwargs +) -> None: + """Store all the env vars from a .env file into the database. + + Args: + table_name: Name of the table to store secrets. + dotenv_file: Dot env filename. + drop_existing: Boolean flag to drop existing table. + """ + main.__init__(**kwargs) + if drop_existing: + LOGGER.info("Dropping table '%s' if available", table_name) + database.drop_table(table_name) + database.create_table(table_name, ["key", "value"]) + else: + try: + if existing := database.get_table(table_name): + LOGGER.warning( + "Table '%s' exists already. %d secrets will be overwritten", + table_name, + len(existing), + ) + except sqlite3.OperationalError as error: + if str(error) == f"no such table: {table_name}": + LOGGER.info("Creating a new table %s", table_name) + database.create_table(table_name, ["key", "value"]) + else: + raise + env_vars = dotenv_values(dotenv_file) + for key, value in env_vars.items(): + 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))