From b934c771e2d840987b194a1e850d57e3ff4b410d Mon Sep 17 00:00:00 2001
From: Vignesh Rao Auto generate a
@@ -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 @@ SECRET
valueEnvironment VariablesOptional (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
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 for more information about decrypting the retrieved secret from the server.
+
SECRET
valueThis value will be used to encrypt/decrypt the secrets stored in the database.
CLI
@@ -131,7 +135,7 @@PEP 8
and isort
Requirement
python -m pip install gitverse
© 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. +SECRET
valueSECRET
valueModule 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.
Decrypts the ciphertext into an appropriate payload.
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 the decrypted payload.
@@ -952,7 +943,7 @@-$9oq6d3JPB*}v>G;$e
z-%Y!pi^*)zn_2t