mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
feat(vaults): support custom headers on API vault
This commit is contained in:
@@ -208,6 +208,9 @@ key_vaults:
|
|||||||
name: myapi
|
name: myapi
|
||||||
uri: https://vault.example.com/api
|
uri: https://vault.example.com/api
|
||||||
token: "your-bearer-token"
|
token: "your-bearer-token"
|
||||||
|
headers:
|
||||||
|
CF-Access-Client-Id: "CLIENT_ID_HERE"
|
||||||
|
CF-Access-Client-Secret: "CLIENT_SECRET_HERE"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Field | Required | Description |
|
| Field | Required | Description |
|
||||||
@@ -218,6 +221,7 @@ key_vaults:
|
|||||||
| `token` | yes | Bearer token, sent as `Authorization: Bearer <token>`. |
|
| `token` | yes | Bearer token, sent as `Authorization: Bearer <token>`. |
|
||||||
| `no_push` | no | If `true`, keys are read but never written. |
|
| `no_push` | no | If `true`, keys are read but never written. |
|
||||||
| `timeout` | no | Request timeout in seconds; defaults to `vault_timeout`. |
|
| `timeout` | no | Request timeout in seconds; defaults to `vault_timeout`. |
|
||||||
|
| `headers` | no | Extra HTTP `headers` sent with every request, for example the Cloudflare Access service-token headers `CF-Access-Client-Id` and `CF-Access-Client-Secret`. |
|
||||||
|
|
||||||
The backend talks to endpoints under `uri` (for example `GET {uri}/{service}/{kid}` to look up a single key) and interprets a numeric `code` field in each JSON response to detect errors such as an invalid token, rate limiting, or an invalid service tag.
|
The backend talks to endpoints under `uri` (for example `GET {uri}/{service}/{kid}` to look up a single key) and interprets a numeric `code` field in each JSON response to detect errors such as an invalid token, rate limiting, or an invalid service tag.
|
||||||
|
|
||||||
|
|||||||
+33
-9
@@ -10,7 +10,7 @@ from unshackle.core.vault import Vault
|
|||||||
class API(Vault):
|
class API(Vault):
|
||||||
"""Key Vault using a simple RESTful HTTP API call."""
|
"""Key Vault using a simple RESTful HTTP API call."""
|
||||||
|
|
||||||
def __init__(self, name: str, uri: str, token: str, no_push: bool = False, timeout: float = 10.0):
|
def __init__(self, name: str, uri: str, token: str, no_push: bool = False, headers: Optional[dict[str, str]] = None, timeout: float = 10.0):
|
||||||
super().__init__(name, no_push)
|
super().__init__(name, no_push)
|
||||||
self.uri = uri.rstrip("/")
|
self.uri = uri.rstrip("/")
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
@@ -18,13 +18,21 @@ class API(Vault):
|
|||||||
self.session.headers.update({"User-Agent": f"unshackle v{__version__}"})
|
self.session.headers.update({"User-Agent": f"unshackle v{__version__}"})
|
||||||
self.session.headers.update({"Authorization": f"Bearer {token}"})
|
self.session.headers.update({"Authorization": f"Bearer {token}"})
|
||||||
|
|
||||||
|
if headers:
|
||||||
|
self.session.headers.update(headers)
|
||||||
|
|
||||||
def get_key(self, kid: Union[UUID, str], service: str) -> Optional[str]:
|
def get_key(self, kid: Union[UUID, str], service: str) -> Optional[str]:
|
||||||
if isinstance(kid, UUID):
|
if isinstance(kid, UUID):
|
||||||
kid = kid.hex
|
kid = kid.hex
|
||||||
|
|
||||||
data = self.session.get(
|
response = self.session.get(
|
||||||
url=f"{self.uri}/{service.lower()}/{kid}", headers={"Accept": "application/json"}, timeout=self.timeout
|
url=f"{self.uri}/{service.lower()}/{kid}",
|
||||||
).json()
|
headers={"Accept": "application/json"},
|
||||||
|
timeout=self.timeout,
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
code = int(data.get("code", 0))
|
code = int(data.get("code", 0))
|
||||||
message = data.get("message")
|
message = data.get("message")
|
||||||
@@ -52,12 +60,15 @@ class API(Vault):
|
|||||||
page = 1
|
page = 1
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data = self.session.get(
|
response = self.session.get(
|
||||||
url=f"{self.uri}/{service.lower()}",
|
url=f"{self.uri}/{service.lower()}",
|
||||||
params={"page": page, "total": 10},
|
params={"page": page, "total": 10},
|
||||||
headers={"Accept": "application/json"},
|
headers={"Accept": "application/json"},
|
||||||
timeout=self.timeout,
|
timeout=self.timeout,
|
||||||
).json()
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
code = int(data.get("code", 0))
|
code = int(data.get("code", 0))
|
||||||
message = data.get("message")
|
message = data.get("message")
|
||||||
@@ -90,12 +101,15 @@ class API(Vault):
|
|||||||
if isinstance(kid, UUID):
|
if isinstance(kid, UUID):
|
||||||
kid = kid.hex
|
kid = kid.hex
|
||||||
|
|
||||||
data = self.session.post(
|
response = self.session.post(
|
||||||
url=f"{self.uri}/{service.lower()}/{kid}",
|
url=f"{self.uri}/{service.lower()}/{kid}",
|
||||||
json={"content_key": key},
|
json={"content_key": key},
|
||||||
headers={"Accept": "application/json"},
|
headers={"Accept": "application/json"},
|
||||||
timeout=self.timeout,
|
timeout=self.timeout,
|
||||||
).json()
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
code = int(data.get("code", 0))
|
code = int(data.get("code", 0))
|
||||||
message = data.get("message")
|
message = data.get("message")
|
||||||
@@ -141,6 +155,7 @@ class API(Vault):
|
|||||||
json={"content_keys": batch_keys},
|
json={"content_keys": batch_keys},
|
||||||
headers={"Accept": "application/json"},
|
headers={"Accept": "application/json"},
|
||||||
timeout=self.timeout,
|
timeout=self.timeout,
|
||||||
|
allow_redirects=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for HTTP errors that suggest batch is too large
|
# Check for HTTP errors that suggest batch is too large
|
||||||
@@ -151,6 +166,7 @@ class API(Vault):
|
|||||||
batch_size = 1
|
batch_size = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
except Exception:
|
except Exception:
|
||||||
# JSON decode error or connection issue - try smaller batch
|
# JSON decode error or connection issue - try smaller batch
|
||||||
@@ -187,7 +203,14 @@ class API(Vault):
|
|||||||
return total_added
|
return total_added
|
||||||
|
|
||||||
def get_services(self) -> Iterator[str]:
|
def get_services(self) -> Iterator[str]:
|
||||||
data = self.session.post(url=self.uri, headers={"Accept": "application/json"}, timeout=self.timeout).json()
|
response = self.session.post(
|
||||||
|
url=self.uri,
|
||||||
|
headers={"Accept": "application/json"},
|
||||||
|
timeout=self.timeout,
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
code = int(data.get("code", 0))
|
code = int(data.get("code", 0))
|
||||||
message = data.get("message")
|
message = data.get("message")
|
||||||
@@ -227,3 +250,4 @@ class Exceptions:
|
|||||||
|
|
||||||
class ContentKeyInvalid(Exception):
|
class ContentKeyInvalid(Exception):
|
||||||
"""The Content Key is invalid."""
|
"""The Content Key is invalid."""
|
||||||
|
|
||||||
Reference in New Issue
Block a user