Skip to content

Commit

Permalink
Set transit time bucketing via env vars
Browse files Browse the repository at this point in the history
Update runbook and decryption logic
  • Loading branch information
dormant-user committed Sep 19, 2024
1 parent 3a7af40 commit b934c77
Show file tree
Hide file tree
Showing 21 changed files with 199 additions and 136 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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.
<details>
<summary>Auto generate a <code>SECRET</code> value</summary>

Expand Down Expand Up @@ -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
Expand All @@ -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/
33 changes: 0 additions & 33 deletions decoders/README.md

This file was deleted.

37 changes: 0 additions & 37 deletions decoders/decoder.py

This file was deleted.

47 changes: 47 additions & 0 deletions decryptors/README.md
Original file line number Diff line number Diff line change
@@ -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
```
37 changes: 31 additions & 6 deletions decoders/decoder.go → decryptors/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
19 changes: 15 additions & 4 deletions decoders/decoder.js → decryptors/decrypt.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand Down
48 changes: 48 additions & 0 deletions decryptors/decrypt.py
Original file line number Diff line number Diff line change
@@ -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()))
4 changes: 2 additions & 2 deletions doc_gen/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ Squire

.. automodule:: vaultapi.squire

Transmitter
===========
Transit
=======

.. automodule:: vaultapi.transit

Expand Down
8 changes: 6 additions & 2 deletions docs/README.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ <h2>Environment Variables<a class="headerlink" href="#environment-variables" tit
<p><strong>Optional (with defaults)</strong></p>
<ul class="simple">
<li><p><strong>TRANSIT_KEY_LENGTH</strong> - AES key length for transit encryption. Defaults to <code class="docutils literal notranslate"><span class="pre">32</span></code></p></li>
<li><p><strong>TRANSIT_TIME_BUCKET</strong> - Interval for which the transit epoch should remain constant. Defaults to <code class="docutils literal notranslate"><span class="pre">60</span></code></p></li>
<li><p><strong>DATABASE</strong> - FilePath to store the secrets’ database. Defaults to <code class="docutils literal notranslate"><span class="pre">secrets.db</span></code></p></li>
<li><p><strong>HOST</strong> - Hostname for the API server. Defaults to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> [OR] <code class="docutils literal notranslate"><span class="pre">localhost</span></code></p></li>
<li><p><strong>PORT</strong> - Port number for the API server. Defaults to <code class="docutils literal notranslate"><span class="pre">9010</span></code></p></li>
Expand All @@ -113,6 +114,9 @@ <h2>Environment Variables<a class="headerlink" href="#environment-variables" tit
<li><p><strong>ALLOWED_ORIGINS</strong> - Origins that are allowed to retrieve secrets.</p></li>
<li><p><strong>ALLOWED_IP_RANGE</strong> - IP range that is allowed to retrieve secrets. <em>(eg: <code class="docutils literal notranslate"><span class="pre">10.112.8.10-210</span></code>)</em></p></li>
</ul>
<blockquote>
<div><p>Checkout <a class="reference external" href="https://github.com/thevickypedia/VaultAPI/blob/main/decryptors">decryptors</a> for more information about decrypting the retrieved secret from the server.</p>
</div></blockquote>
<details>
<summary>Auto generate a <code>SECRET</code> value</summary><p>This value will be used to encrypt/decrypt the secrets stored in the database.</p>
<p><strong>CLI</strong></p>
Expand All @@ -131,7 +135,7 @@ <h2>Coding Standards<a class="headerlink" href="#coding-standards" title="Permal
Styling conventions: <a class="reference external" href="https://www.python.org/dev/peps/pep-0008/"><code class="docutils literal notranslate"><span class="pre">PEP</span> <span class="pre">8</span></code></a> and <a class="reference external" href="https://pycqa.github.io/isort/"><code class="docutils literal notranslate"><span class="pre">isort</span></code></a></p>
</section>
<section id="release-notes">
<h2><a class="reference external" href="https://github.com/thevickypedia/VaultAPI/blob/master/release_notes.rst">Release Notes</a><a class="headerlink" href="#release-notes" title="Permalink to this heading"></a></h2>
<h2><a class="reference external" href="https://github.com/thevickypedia/VaultAPI/blob/main/release_notes.rst">Release Notes</a><a class="headerlink" href="#release-notes" title="Permalink to this heading"></a></h2>
<p><strong>Requirement</strong></p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>python<span class="w"> </span>-m<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>gitverse
</pre></div>
Expand Down Expand Up @@ -172,7 +176,7 @@ <h2>Runbook<a class="headerlink" href="#runbook" title="Permalink to this headin
<section id="license-copyright">
<h2>License &amp; copyright<a class="headerlink" href="#license-copyright" title="Permalink to this heading"></a></h2>
<p>© Vignesh Rao</p>
<p>Licensed under the <a class="reference external" href="https://github.com/thevickypedia/VaultAPI/blob/master/LICENSE">MIT License</a></p>
<p>Licensed under the <a class="reference external" href="https://github.com/thevickypedia/VaultAPI/blob/main/LICENSE">MIT License</a></p>
</section>
</section>

Expand Down
8 changes: 6 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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.
<details>
<summary>Auto generate a <code>SECRET</code> value</summary>

Expand Down Expand Up @@ -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
Expand All @@ -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/
Loading

0 comments on commit b934c77

Please sign in to comment.