* feat(terraform): add litellm_jwt_key_mapping resource Adds a Terraform resource for the proxy's JWT to virtual key mappings, so a JWT client identified by a claim such as client_id, azp or sub maps to a virtual key and inherits its models, budgets, rate limits and spend tracking. Covers the four mapping endpoints: /jwt/key/mapping/new, /info, /update and /delete. is_active is applied through a follow-up update because the create endpoint always starts a mapping active, a dropped description is sent as an empty string because the update endpoint ignores absent fields, changing the mapped key rotates it in place, and changing the claim name or value forces replacement since the update endpoint cannot change them. * fix(terraform): revert key on failed jwt_key_mapping update Classic SDKv2 persists a failed Update's diff-applied values to state regardless of the error, so a rejected key rotation left the new key in state while the proxy kept the old one and the next plan falsely converged. Revert key via GetChange and resync description/is_active/computed fields from a post-failure Read, since Read alone can't recover key (the proxy never returns it). Also drop the case-insensitive "mapping not found" body match: the proxy raises 404 for all three not-found paths (info, update, delete), so checking the status code alone is sufficient. Clarify the docs: referencing a litellm_key resource's write-only key is not a null-then-400 situation, it's a static "Missing required argument" error at plan time, in every apply ordering. * fix(terraform): stop leaving an active mapping behind on failed cleanup Two issues flagged by review: - Create has no way to ask the proxy for an inactive mapping, so an is_active=false mapping is briefly active while the follow-up deactivation runs. If that deactivation call itself fails, the mapping used to stay active and untracked. It's now deleted instead, closing the exposure rather than leaving it open indefinitely. - On a failed update, only `key` was reverted before the recovery read. If that read also failed, description/is_active kept the rejected values, so a later plan could report false convergence. Now all three are reverted before the read runs. Both come with regression tests, mutation-verified against the pre-fix code. * fix(deps): bump restrictedpython to 8.5 for GHSA-ffg3-p8fm-mjx2 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore: retrigger ci Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(tests): stub anthropic judge credentials in funnel seeding test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * revert(deps): keep uv.lock unchanged to keep the PR terraform-only Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Fabrice Pont <fabrice.pont@doctolib.com> Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
4.3 KiB
litellm_jwt_key_mapping
Maps a JWT claim value to a LiteLLM virtual key. Every JWT client identified by a claim, typically client_id, azp or sub, then gets the model restrictions, budgets, rate limits, guardrails and spend tracking of the virtual key it maps to, without that key ever being handed to the client.
The mappings only take effect once JWT auth is enabled on the proxy, which is configuration rather than API state:
general_settings:
enable_jwt_auth: True
litellm_jwtauth:
virtual_key_claim_field: "client_id"
unregistered_jwt_client_behavior: "fallback_team_mapping"
See JWT to virtual key mapping for the proxy side of the feature
Example Usage
The mapped virtual key has to exist already and its value has to be known to Terraform, so it comes from a variable or a secret manager rather than from a litellm_key resource. litellm_key deliberately made its generated key write-only, to avoid storing raw API keys in state, so referencing it here does not merely read back null: Terraform's write-only enforcement turns key = litellm_key.foo.key into a static Missing required argument error at terraform plan, before any API call, in every apply ordering, including a first apply where both resources are created together:
variable "alice_key" {
type = string
sensitive = true
}
resource "litellm_jwt_key_mapping" "alice" {
jwt_claim_name = "client_id"
jwt_claim_value = "dev-alice"
key = var.alice_key
}
Per-client limits live on the virtual key, so one mapping per client is how each JWT client gets its own budget and quota:
resource "litellm_jwt_key_mapping" "billing_service" {
jwt_claim_name = "client_id"
jwt_claim_value = "billing-service"
key = var.billing_service_key
description = "Billing service JWT client"
is_active = true
}
Several clients at once, with the key values coming from a map of secrets:
variable "jwt_client_keys" {
type = map(string)
sensitive = true
}
resource "litellm_jwt_key_mapping" "developer" {
for_each = var.jwt_client_keys
jwt_claim_name = "client_id"
jwt_claim_value = each.key
key = each.value
description = "Developer JWT client ${each.key}"
}
Argument Reference
jwt_claim_name- (Required, ForceNew) Name of the JWT claim to match on, for exampleclient_id,azporsub. Must matchvirtual_key_claim_fieldin the proxy JWT configjwt_claim_value- (Required, ForceNew) Value of the claim identifying the JWT client. Unique together withjwt_claim_name, so a second mapping for the same pair fails with a 409key- (Required, Sensitive) The virtual key this claim value maps to. It has to exist already, otherwise the proxy rejects the mapping withThe provided key does not match an existing virtual keydescription- (Optional) Description of the mappingis_active- (Optional) Whether the mapping is active. Inactive mappings are ignored during JWT auth. Defaults totrue
Attribute Reference
id- The mapping ID assigned by LiteLLMcreated_at- Timestamp when the mapping was createdupdated_at- Timestamp when the mapping was last updatedcreated_by- User who created the mappingupdated_by- User who last updated the mapping
Notes
The proxy stores only a hash of key and never returns it, so drift on that attribute cannot be detected and Terraform tracks the value from your configuration. Changing key rotates the mapping onto the new virtual key in place, with no replacement. Like the other secrets this provider accepts, such as credential_values and model_api_key, the configured value is kept in state, so treat the state as sensitive
Only proxy admins can create, update or delete mappings, so the provider api_key has to be a master key or an admin key
Import
Mappings are imported by their mapping ID:
terraform import litellm_jwt_key_mapping.alice 297a5536-1aeb-4cf1-b666-b3809c2750a8
Because the API does not return the mapped key, key is empty in state right after an import, so the first plan shows an in-place update that pushes the configured key back to the proxy. That update is harmless, the proxy just rehashes the same value when the key has not actually changed