mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-10 13:31:38 +00:00
feat(egress): first-class x-api-key providers + hot reload via management API
Both wired against features the iron-proxy author (@mslipper) confirmed on PR #30179 — and both verified present in the pinned v0.39.0 source. Header-auth providers (match_headers): - New _HEADER_AUTH_PROVIDERS: Anthropic native (x-api-key), Azure OpenAI (api-key on *.openai.azure.com / *.cognitiveservices / *.services.ai), Gemini (x-goog-api-key + ?key= query param via match_query). - TokenMapping grows match_headers + alias_env_names; per-provider header sets flow into the secrets rules; mappings.json roundtrips them (legacy files load with the Authorization default). - GEMINI_API_KEY / GOOGLE_API_KEY collapse into ONE mapping (two require-rules on the same host would reject each other); the sandbox gets the token under both names, and the proxy child env mirrors the alias into the canonical name when only the alias is set. - Docker backend injects alias env names alongside canonical ones. - The fail-closed tier is now empty, so fail_on_uncovered_providers and discover_blocked_providers are deleted (dead toggle otherwise); _NON_BEARER_PROVIDERS shrinks to genuinely-unswappable signature auth (AWS SigV4, GCP service-account OAuth) — warn-only, as before. Management API (hot reload): - Generated proxy.yaml enables the v0.39 management listener: loopback only at tunnel_port+2, bearer key from HERMES_IRON_PROXY_MGMT_KEY. - Key minted at setup (management.token, 0600); start_proxy injects it (v0.39 refuses to start when api_key_env is empty). - hermes egress reload -> POST /v1/reload: re-reads proxy.yaml and atomically swaps the pipeline; 422 leaves the running ruleset untouched; actionable errors for not-running / pre-management config / key mismatch. Secrets changes still require restart (daemon env is read at spawn) — the CLI says so. Validation: 218/218 unit+CLI+docker tests; 3/3 gated live E2E against the real v0.39.0 binary (Authorization swap, x-api-key swap, live reload with token rotation on the same pid). Docs updated.
This commit is contained in:
parent
6ff7e78649
commit
86fcb2fe5f
10 changed files with 922 additions and 250 deletions
|
|
@ -102,7 +102,6 @@ hermes egress setup [--from-bitwarden | --no-bitwarden] [--rotate-tokens]
|
|||
hermes egress start
|
||||
-> proxy_cli.cmd_start
|
||||
Pre-checks (refuse-start path):
|
||||
- proxy.fail_on_uncovered_providers? -> discover_blocked_providers()
|
||||
- credential_source=bitwarden? -> pre-validate access_token_env + project_id
|
||||
-> iron_proxy.start_proxy(
|
||||
refresh_secrets_from_bitwarden=...,
|
||||
|
|
@ -249,19 +248,31 @@ _BEARER_PROVIDERS: Dict[str, Tuple[str, ...]] = {
|
|||
|
||||
Also update `_DEFAULT_ALLOWED_HOSTS` so the proxy allows the upstream by default. Run `test_discover_provider_mappings_*` to confirm.
|
||||
|
||||
### Adding a new non-bearer provider
|
||||
### Adding a new header-token provider (x-api-key family)
|
||||
|
||||
If the provider uses `x-api-key` / SigV4 / OAuth-from-SDK / etc., iron-proxy's `secrets` transform cannot swap it. Add the env var to `_NON_BEARER_PROVIDERS` so the wizard warns about it. If the provider is LLM-specific enough that you want `fail_on_uncovered_providers: true` to actually block it, also add to `_LLM_SPECIFIC_NON_BEARER_PROVIDERS`.
|
||||
If the provider authenticates with a static NON-Authorization header (like Anthropic's `x-api-key`, Azure's `api-key`, or Gemini's `x-goog-api-key`), add it to `_HEADER_AUTH_PROVIDERS` — iron-proxy's `secrets.replace.match_headers` targets arbitrary header names, so these are first-class swapped providers:
|
||||
|
||||
```python
|
||||
_HEADER_AUTH_PROVIDERS: Dict[str, Dict[str, Tuple[str, ...]]] = {
|
||||
...,
|
||||
"MY_PROVIDER_API_KEY": {
|
||||
"hosts": ("api.myprovider.com",),
|
||||
"match_headers": ("x-my-auth-header", "Authorization"),
|
||||
"aliases": (),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Use `aliases` ONLY for interchangeable env-var names of the *same* credential (e.g. `GOOGLE_API_KEY` for `GEMINI_API_KEY`) — aliased names collapse into a single mapping, because two `require: true` rules on the same host reject each other's requests. Also update `_DEFAULT_ALLOWED_HOSTS`.
|
||||
|
||||
### Adding a new signature-auth provider (uncovered)
|
||||
|
||||
If the provider uses SigV4 / SDK-minted OAuth / request signatures, a static header swap cannot cover it. Add the env var to `_NON_BEARER_PROVIDERS` so the wizard and `hermes egress status` warn about it:
|
||||
|
||||
```python
|
||||
_NON_BEARER_PROVIDERS: Tuple[str, ...] = (
|
||||
...,
|
||||
"MY_X_API_KEY_PROVIDER",
|
||||
)
|
||||
|
||||
_LLM_SPECIFIC_NON_BEARER_PROVIDERS: Tuple[str, ...] = (
|
||||
...,
|
||||
"MY_X_API_KEY_PROVIDER",
|
||||
"MY_SIGNED_PROVIDER_ACCESS_KEY",
|
||||
)
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue