diff --git a/README.md b/README.md index ae3559e..48e2b84 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ vaultapi start **Optional (with defaults)** - **TRANSIT_KEY_LENGTH** - AES key length for transit encryption. Defaults to `32` +- **TRANSIT_TIME_BUCKET** - Interval for which the transit epoch should remain constant. Defaults to `60` - **DATABASE** - FilePath to store the secrets' database. Defaults to `secrets.db` - **HOST** - Hostname for the API server. Defaults to `0.0.0.0` [OR] `localhost` - **PORT** - Port number for the API server. Defaults to `9010` @@ -75,6 +76,8 @@ Defaults to 5req/2s [AND] 10req/30s - **ALLOWED_ORIGINS** - Origins that are allowed to retrieve secrets. - **ALLOWED_IP_RANGE** - IP range that is allowed to retrieve secrets. _(eg: `10.112.8.10-210`)_ +> Checkout [decryptors][decryptors] for more information about decrypting the retrieved secret from the server. +
Auto generate a SECRET value @@ -159,7 +162,8 @@ Licensed under the [MIT License][license] [3.10]: https://docs.python.org/3/whatsnew/3.10.html [3.11]: https://docs.python.org/3/whatsnew/3.11.html [virtual environment]: https://docs.python.org/3/tutorial/venv.html -[release-notes]: https://github.com/thevickypedia/VaultAPI/blob/master/release_notes.rst +[release-notes]: https://github.com/thevickypedia/VaultAPI/blob/main/release_notes.rst +[decryptors]: https://github.com/thevickypedia/VaultAPI/blob/main/decryptors [gha_pages]: https://github.com/thevickypedia/VaultAPI/actions/workflows/pages/pages-build-deployment [gha_docker]: https://github.com/thevickypedia/VaultAPI/actions/workflows/docker-publish.yaml [gha_docker_desc]: https://github.com/thevickypedia/VaultAPI/actions/workflows/docker-description.yaml @@ -175,5 +179,5 @@ Licensed under the [MIT License][license] [pypi]: https://pypi.org/project/VaultAPI [pypi-files]: https://pypi.org/project/VaultAPI/#files [pypi-repo]: https://packaging.python.org/tutorials/packaging-projects/ -[license]: https://github.com/thevickypedia/VaultAPI/blob/master/LICENSE +[license]: https://github.com/thevickypedia/VaultAPI/blob/main/LICENSE [runbook]: https://thevickypedia.github.io/VaultAPI/ diff --git a/decoders/README.md b/decoders/README.md deleted file mode 100644 index e060cb1..0000000 --- a/decoders/README.md +++ /dev/null @@ -1,33 +0,0 @@ -## Transit decryption in various languages - - -### Python - -**Install requirements** -```shell -pip install requests cryptography -``` - -**Run decoder** -```shell -python decoder.py -``` - -### Go lang - -**Run decoder** -```shell -go run decoder.go -``` - -### JavaScript - -**Install requirements** -```shell -npm install axios -``` - -**Run decoder** -```shell -node decoder.js -``` diff --git a/decoders/decoder.py b/decoders/decoder.py deleted file mode 100644 index 8e4d2f2..0000000 --- a/decoders/decoder.py +++ /dev/null @@ -1,37 +0,0 @@ -import base64 -import hashlib -import json -import os -import time -from typing import Dict, ByteString, Any - -import requests -from cryptography.hazmat.primitives.ciphers.aead import AESGCM - -APIKEY = os.environ["APIKEY"] - - -def transit_decrypt(ciphertext: str | ByteString, key_length: int = 32) -> Dict[str, Any]: - epoch = int(time.time()) // 60 - hash_object = hashlib.sha256(f"{epoch}.{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) - - -def get_cipher(): - headers = { - 'accept': 'application/json', - 'Authorization': f'Bearer {APIKEY}', - } - params = { - 'table_name': 'default', - } - response = requests.get('http://0.0.0.0:8080/get-table', params=params, headers=headers) - assert response.ok, response.text - return response.json()['detail'] - - -print(transit_decrypt(ciphertext=get_cipher())) diff --git a/decryptors/README.md b/decryptors/README.md new file mode 100644 index 0000000..0ec9415 --- /dev/null +++ b/decryptors/README.md @@ -0,0 +1,47 @@ +# Transit Protection + +VaultAPI includes an added security feature that protects retrieved secrets during transit to the client. + +1. Decrypts the requested secret values from the database (uses Fernet algorithm) +2. Constructs a payload with the requested key-value pairs. +3. Encrypts the payload with the API key and a timestamp that's valid for 60s + +### Other security recommendations + +- Set `ALLOWED_ORIGINS` to known origins, consider using reverse-proxy if the origin is public facing. +- Set `ALLOWED_IP_RANGE` to known IPv4 address range, to allow access only to specific IP addresses. +- Set `TRANSIT_KEY_LENGTH` to strong value (`16`/`24`/`32`...) to increase transit security. +- Set `TRANSIT_TIME_BUCKET` to a lower value to set the decryption timeframe to a minimum. + +### Transit decryption logic in various languages + +### Python + +**Install requirements** +```shell +pip install requests cryptography +``` + +**Run decrypt** +```shell +python decrypt.py +``` + +### Go lang + +**Run decrypt** +```shell +go run decrypt.go +``` + +### JavaScript + +**Install requirements** +```shell +npm install axios +``` + +**Run decrypt** +```shell +node decrypt.js +``` diff --git a/decoders/decoder.go b/decryptors/decrypt.go similarity index 61% rename from decoders/decoder.go rename to decryptors/decrypt.go index 4d3a628..5cc6807 100644 --- a/decoders/decoder.go +++ b/decryptors/decrypt.go @@ -10,15 +10,39 @@ import ( "net/http" "os" "time" + "strconv" ) var apiKey = os.Getenv("APIKEY") -func transitDecrypt(ciphertext string, keyLength int) (map[string]interface{}, error) { - epoch := time.Now().Unix() / 60 +func getEnvInt(key string, defaultValue int64) int64 { + if value, exists := os.LookupEnv(key); exists { + if intValue, err := strconv.ParseInt(value, 10, 64); err == nil { + return intValue + } + } + return defaultValue +} + +func getEnvString(key, defaultValue string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return defaultValue +} + +var ( + TRANSIT_TIME_BUCKET = getEnvInt("TRANSIT_TIME_BUCKET", 60) + TRANSIT_KEY_LENGTH = getEnvInt("TRANSIT_KEY_LENGTH", 32) + HOST = getEnvString("HOST", "0.0.0.0") + PORT = getEnvInt("PORT", 8080) +) + +func transitDecrypt(ciphertext string) (map[string]interface{}, error) { + epoch := time.Now().Unix() / TRANSIT_TIME_BUCKET hash := sha256.New() hash.Write([]byte(fmt.Sprintf("%d.%s", epoch, apiKey))) - aesKey := hash.Sum(nil)[:keyLength] + aesKey := hash.Sum(nil)[:TRANSIT_KEY_LENGTH] cipherBytes, err := base64.StdEncoding.DecodeString(ciphertext) if err != nil { @@ -53,7 +77,8 @@ func transitDecrypt(ciphertext string, keyLength int) (map[string]interface{}, e } func getCipher() (string, error) { - req, err := http.NewRequest("GET", "http://0.0.0.0:8080/get-table", nil) + var url = fmt.Sprintf("http://%s:%d/get-table", HOST, PORT) + req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } @@ -92,11 +117,11 @@ func main() { return } - decryptedData, err := transitDecrypt(ciphertext, 32) + decryptedData, err := transitDecrypt(ciphertext) if err != nil { fmt.Println("Error decrypting:", err) return } - fmt.Println("Decrypted data:", decryptedData) + fmt.Println(decryptedData) } diff --git a/decoders/decoder.js b/decryptors/decrypt.js similarity index 69% rename from decoders/decoder.js rename to decryptors/decrypt.js index f128618..14050bc 100644 --- a/decoders/decoder.js +++ b/decryptors/decrypt.js @@ -3,11 +3,22 @@ const axios = require('axios'); const APIKEY = process.env.APIKEY; -async function transitDecrypt(ciphertext, keyLength = 32) { - const epoch = Math.floor(Date.now() / 60000); +const getEnvAsInt = (key, defaultValue) => { + const value = process.env[key]; + return value !== undefined ? parseInt(value, 10) : defaultValue; +}; + +const TRANSIT_TIME_BUCKET = getEnvAsInt("TRANSIT_TIME_BUCKET", 60); +const TRANSIT_KEY_LENGTH = getEnvAsInt("TRANSIT_KEY_LENGTH", 60); +const HOST = process.env.HOST || "0.0.0.0"; +const PORT = getEnvAsInt("PORT", 8080); + + +async function transitDecrypt(ciphertext) { + const epoch = Math.floor(Date.now() / (1000 * TRANSIT_TIME_BUCKET)); const hash = crypto.createHash('sha256'); hash.update(`${epoch}.${APIKEY}`); - const aesKey = hash.digest().slice(0, keyLength); + const aesKey = hash.digest().slice(0, TRANSIT_KEY_LENGTH); const bufferCiphertext = Buffer.from(ciphertext, 'base64'); if (bufferCiphertext.length < 12 + 16) { @@ -39,7 +50,7 @@ async function getCipher() { const params = { table_name: 'default', }; - const response = await axios.get('http://0.0.0.0:8080/get-table', {params, headers}); + const response = await axios.get(`http://${HOST}:${PORT}/get-table`, {params, headers}); // noqa: HttpUrlsUsage if (response.status !== 200) { throw new Error(response.data); } diff --git a/decryptors/decrypt.py b/decryptors/decrypt.py new file mode 100644 index 0000000..bef16ce --- /dev/null +++ b/decryptors/decrypt.py @@ -0,0 +1,48 @@ +import base64 +import hashlib +import json +import os +import time +from typing import Any, ByteString, Dict + +import requests +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + +APIKEY = os.environ["APIKEY"] + +TRANSIT_TIME_BUCKET = os.environ.get("TRANSIT_TIME_BUCKET", 60) +TRANSIT_KEY_LENGTH = os.environ.get("TRANSIT_KEY_LENGTH", 60) +HOST = os.environ.get("HOST", "0.0.0.0") +PORT = os.environ.get("PORT", 8080) + + +def transit_decrypt(ciphertext: str | ByteString) -> Dict[str, Any]: + """Decrypt transit encrypted payload.""" + epoch = int(time.time()) // TRANSIT_TIME_BUCKET + hash_object = hashlib.sha256(f"{epoch}.{APIKEY}".encode()) + aes_key = hash_object.digest()[:TRANSIT_KEY_LENGTH] + if isinstance(ciphertext, str): + ciphertext = base64.b64decode(ciphertext) + decrypted = AESGCM(aes_key).decrypt(ciphertext[:12], ciphertext[12:], b"") + return json.loads(decrypted) + + +def get_cipher() -> str: + """Get ciphertext from the server.""" + headers = { + "accept": "application/json", + "Authorization": f"Bearer {APIKEY}", + } + params = { + "table_name": "default", + } + response = requests.get( + f"http://{HOST}:{PORT}/get-table", # noqa: HttpUrlsUsage + params=params, + headers=headers, + ) + assert response.ok, response.text + return response.json()["detail"] + + +print(transit_decrypt(ciphertext=get_cipher())) diff --git a/doc_gen/index.rst b/doc_gen/index.rst index 36ad598..bded1b9 100644 --- a/doc_gen/index.rst +++ b/doc_gen/index.rst @@ -76,8 +76,8 @@ Squire .. automodule:: vaultapi.squire -Transmitter -=========== +Transit +======= .. automodule:: vaultapi.transit diff --git a/docs/README.html b/docs/README.html index b8da763..f7fa508 100644 --- a/docs/README.html +++ b/docs/README.html @@ -100,6 +100,7 @@

Environment VariablesOptional (with defaults)

+
+

Checkout decryptors for more information about decrypting the retrieved secret from the server.

+
Auto generate a SECRET value

This value will be used to encrypt/decrypt the secrets stored in the database.

CLI

@@ -131,7 +135,7 @@

Coding StandardsPEP 8 and isort

-

Release Notes

+

Release Notes

Requirement

python -m pip install gitverse
 
@@ -172,7 +176,7 @@

Runbook

License & copyright

© Vignesh Rao

-

Licensed under the MIT License

+

Licensed under the MIT License

diff --git a/docs/README.md b/docs/README.md index ae3559e..48e2b84 100644 --- a/docs/README.md +++ b/docs/README.md @@ -63,6 +63,7 @@ vaultapi start **Optional (with defaults)** - **TRANSIT_KEY_LENGTH** - AES key length for transit encryption. Defaults to `32` +- **TRANSIT_TIME_BUCKET** - Interval for which the transit epoch should remain constant. Defaults to `60` - **DATABASE** - FilePath to store the secrets' database. Defaults to `secrets.db` - **HOST** - Hostname for the API server. Defaults to `0.0.0.0` [OR] `localhost` - **PORT** - Port number for the API server. Defaults to `9010` @@ -75,6 +76,8 @@ Defaults to 5req/2s [AND] 10req/30s - **ALLOWED_ORIGINS** - Origins that are allowed to retrieve secrets. - **ALLOWED_IP_RANGE** - IP range that is allowed to retrieve secrets. _(eg: `10.112.8.10-210`)_ +> Checkout [decryptors][decryptors] for more information about decrypting the retrieved secret from the server. +
Auto generate a SECRET value @@ -159,7 +162,8 @@ Licensed under the [MIT License][license] [3.10]: https://docs.python.org/3/whatsnew/3.10.html [3.11]: https://docs.python.org/3/whatsnew/3.11.html [virtual environment]: https://docs.python.org/3/tutorial/venv.html -[release-notes]: https://github.com/thevickypedia/VaultAPI/blob/master/release_notes.rst +[release-notes]: https://github.com/thevickypedia/VaultAPI/blob/main/release_notes.rst +[decryptors]: https://github.com/thevickypedia/VaultAPI/blob/main/decryptors [gha_pages]: https://github.com/thevickypedia/VaultAPI/actions/workflows/pages/pages-build-deployment [gha_docker]: https://github.com/thevickypedia/VaultAPI/actions/workflows/docker-publish.yaml [gha_docker_desc]: https://github.com/thevickypedia/VaultAPI/actions/workflows/docker-description.yaml @@ -175,5 +179,5 @@ Licensed under the [MIT License][license] [pypi]: https://pypi.org/project/VaultAPI [pypi-files]: https://pypi.org/project/VaultAPI/#files [pypi-repo]: https://packaging.python.org/tutorials/packaging-projects/ -[license]: https://github.com/thevickypedia/VaultAPI/blob/master/LICENSE +[license]: https://github.com/thevickypedia/VaultAPI/blob/main/LICENSE [runbook]: https://thevickypedia.github.io/VaultAPI/ diff --git a/docs/_sources/README.md.txt b/docs/_sources/README.md.txt index ae3559e..48e2b84 100644 --- a/docs/_sources/README.md.txt +++ b/docs/_sources/README.md.txt @@ -63,6 +63,7 @@ vaultapi start **Optional (with defaults)** - **TRANSIT_KEY_LENGTH** - AES key length for transit encryption. Defaults to `32` +- **TRANSIT_TIME_BUCKET** - Interval for which the transit epoch should remain constant. Defaults to `60` - **DATABASE** - FilePath to store the secrets' database. Defaults to `secrets.db` - **HOST** - Hostname for the API server. Defaults to `0.0.0.0` [OR] `localhost` - **PORT** - Port number for the API server. Defaults to `9010` @@ -75,6 +76,8 @@ Defaults to 5req/2s [AND] 10req/30s - **ALLOWED_ORIGINS** - Origins that are allowed to retrieve secrets. - **ALLOWED_IP_RANGE** - IP range that is allowed to retrieve secrets. _(eg: `10.112.8.10-210`)_ +> Checkout [decryptors][decryptors] for more information about decrypting the retrieved secret from the server. +
Auto generate a SECRET value @@ -159,7 +162,8 @@ Licensed under the [MIT License][license] [3.10]: https://docs.python.org/3/whatsnew/3.10.html [3.11]: https://docs.python.org/3/whatsnew/3.11.html [virtual environment]: https://docs.python.org/3/tutorial/venv.html -[release-notes]: https://github.com/thevickypedia/VaultAPI/blob/master/release_notes.rst +[release-notes]: https://github.com/thevickypedia/VaultAPI/blob/main/release_notes.rst +[decryptors]: https://github.com/thevickypedia/VaultAPI/blob/main/decryptors [gha_pages]: https://github.com/thevickypedia/VaultAPI/actions/workflows/pages/pages-build-deployment [gha_docker]: https://github.com/thevickypedia/VaultAPI/actions/workflows/docker-publish.yaml [gha_docker_desc]: https://github.com/thevickypedia/VaultAPI/actions/workflows/docker-description.yaml @@ -175,5 +179,5 @@ Licensed under the [MIT License][license] [pypi]: https://pypi.org/project/VaultAPI [pypi-files]: https://pypi.org/project/VaultAPI/#files [pypi-repo]: https://packaging.python.org/tutorials/packaging-projects/ -[license]: https://github.com/thevickypedia/VaultAPI/blob/master/LICENSE +[license]: https://github.com/thevickypedia/VaultAPI/blob/main/LICENSE [runbook]: https://thevickypedia.github.io/VaultAPI/ diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index 36ad598..bded1b9 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -76,8 +76,8 @@ Squire .. automodule:: vaultapi.squire -Transmitter -=========== +Transit +======= .. automodule:: vaultapi.transit diff --git a/docs/genindex.html b/docs/genindex.html index ae9628b..214c75b 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -73,8 +73,6 @@

_

A

diff --git a/docs/index.html b/docs/index.html index dc0ca9b..3dd769b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -95,11 +95,6 @@

Welcome to VaultAPI’s documentation!

Authenticator

-
-
-vaultapi.auth.EPOCH()
-
-
async vaultapi.auth.validate(request: Request, apikey: HTTPAuthorizationCredentials) None
@@ -273,11 +268,6 @@

Modelsfernet: cryptography.fernet.Fernet | None

-
-
-aes_key: Optional[ByteString]
-
-
info: Dict[str, str]
@@ -329,6 +319,11 @@

Modelstransit_key_length: int

+
+
+transit_time_bucket: int
+
+
database: Union[Path, Path, str]
@@ -810,7 +805,7 @@

Payload -

Transmitter

+

Transit

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.

@@ -903,15 +898,11 @@

Payload
-vaultapi.util.transit_decrypt(apikey: str, ciphertext: Union[str, ByteString], key_length: int = 32) Dict[str, Any]
+vaultapi.util.transit_decrypt(ciphertext: Union[str, ByteString]) Dict[str, Any]

Decrypts the ciphertext into an appropriate payload.

Parameters:
-
    -
  • apikey – API key that was used to encrypt the payload.

  • -
  • ciphertext – Encrypted ciphertext.

  • -
  • key_length – AES key size used during encryption.

  • -
+

ciphertext – Encrypted ciphertext.

Returns:

Returns the decrypted payload.

@@ -952,7 +943,7 @@

Table of Contents

  • RateLimit
  • API Routes
  • Squire
  • -
  • Transmitter
  • +
  • Transit
  • Util
  • Indices and tables
  • diff --git a/docs/objects.inv b/docs/objects.inv index 7ed4dc8..4d763a2 100644 Binary files a/docs/objects.inv and b/docs/objects.inv differ diff --git a/docs/searchindex.js b/docs/searchindex.js index 77f1855..b4cffed 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, 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 +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, "_": 0, "length": [0, 1], "ae": [0, 1], "32": [0, 1], "time": [0, 1], "bucket": 0, "interv": 0, "which": [0, 1], "epoch": 0, "should": 0, "remain": 0, "constant": 0, "60": 0, "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, "checkout": 0, "decryptor": 0, "more": 0, "inform": [0, 1], "about": 0, "decrypt": [0, 1], "auto": 0, "gener": [0, 1], "thi": [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, "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, "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, "info": 1, "rp": 1, "allowed_origin": 1, "arbitrari": 1, "arbitrary_types_allow": 1, "true": 1, "envconfig": 1, "baseset": 1, "transit_key_length": 1, "transit_time_bucket": 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, "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, "bytestr": 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, "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, "", "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, 4, 1, "", "transit_time_bucket"], [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, "", "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, "transit": 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 115228e..1567f27 100644 --- a/vaultapi/auth.py +++ b/vaultapi/auth.py @@ -1,6 +1,5 @@ import logging import secrets -import time from http import HTTPStatus from fastapi import Request @@ -9,7 +8,6 @@ from . import exceptions, models LOGGER = logging.getLogger("uvicorn.default") -EPOCH = lambda: int(time.time()) # noqa: E731 SECURITY = HTTPBearer() diff --git a/vaultapi/main.py b/vaultapi/main.py index cea45ab..542b5bd 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, transit, version +from . import database, models, routes, squire, version LOGGER = logging.getLogger("uvicorn.default") VaultAPI = FastAPI( diff --git a/vaultapi/models.py b/vaultapi/models.py index 10eb4fb..ae6efdc 100644 --- a/vaultapi/models.py +++ b/vaultapi/models.py @@ -2,7 +2,7 @@ import re import socket import sqlite3 -from typing import Any, ByteString, Dict, List, Set +from typing import Any, Dict, List, Set from cryptography.fernet import Fernet from pydantic import ( @@ -117,6 +117,7 @@ class EnvConfig(BaseSettings): apikey: str secret: str transit_key_length: PositiveInt = 32 + transit_time_bucket: PositiveInt = 60 database: FilePath | NewPath | str = Field("secrets.db", pattern=".*.db$") host: str = socket.gethostbyname("localhost") or "0.0.0.0" port: PositiveInt = 9010 diff --git a/vaultapi/transit.py b/vaultapi/transit.py index df51318..ecf9adc 100644 --- a/vaultapi/transit.py +++ b/vaultapi/transit.py @@ -49,8 +49,10 @@ def encrypt(payload: Dict[str, Any], url_safe: bool = True) -> ByteString | str: """ nonce = secrets.token_bytes(12) encoded = json.dumps(payload).encode() - epoch = int(time.time()) // 60 - aes_key = string_to_aes_key(f"{epoch}.{models.env.apikey}", models.env.transit_key_length) + epoch = int(time.time()) // models.env.transit_time_bucket + aes_key = string_to_aes_key( + f"{epoch}.{models.env.apikey}", models.env.transit_key_length + ) ciphertext = nonce + AESGCM(aes_key).encrypt(nonce, encoded, b"") if url_safe: return base64.b64encode(ciphertext).decode("utf-8") @@ -69,11 +71,11 @@ def decrypt(ciphertext: ByteString | str) -> Dict[str, Any]: """ if isinstance(ciphertext, str): ciphertext = base64.b64decode(ciphertext) - epoch = int(time.time()) // 60 - aes_key = string_to_aes_key(f"{epoch}.{models.env.apikey}", models.env.transit_key_length) - decrypted = AESGCM(aes_key).decrypt( - ciphertext[:12], ciphertext[12:], b"" + epoch = int(time.time()) // models.env.transit_time_bucket + aes_key = string_to_aes_key( + f"{epoch}.{models.env.apikey}", models.env.transit_key_length ) + decrypted = AESGCM(aes_key).decrypt(ciphertext[:12], ciphertext[12:], b"") return json.loads(decrypted) diff --git a/vaultapi/util.py b/vaultapi/util.py index 5f5dd90..7e8421f 100644 --- a/vaultapi/util.py +++ b/vaultapi/util.py @@ -1,10 +1,10 @@ -import time import base64 import hashlib import importlib import json import logging import sqlite3 +import time from typing import Any, ByteString, Dict from cryptography.hazmat.primitives.ciphers.aead import AESGCM @@ -60,23 +60,19 @@ def dotenv_to_table( 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]: +def transit_decrypt(ciphertext: str | ByteString) -> 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. """ - epoch = int(time.time()) // 60 - hash_object = hashlib.sha256(f"{epoch}.{apikey}".encode()) - aes_key = hash_object.digest()[:key_length] + epoch = int(time.time()) // models.env.transit_time_bucket + hash_object = hashlib.sha256(f"{epoch}.{models.env.apikey}".encode()) + aes_key = hash_object.digest()[: models.env.transit_key_length] if isinstance(ciphertext, str): ciphertext = base64.b64decode(ciphertext) decrypted = AESGCM(aes_key).decrypt(ciphertext[:12], ciphertext[12:], b"")