feat(terraform-provider): support custom HTTP headers on provider

Allow optional custom_headers on the litellm provider so every API
request can carry proxy or gateway headers. x-api-key always wins
This commit is contained in:
Matthijs Vogel 2026-08-10 18:34:56 +02:00
parent 33d7e3f862
commit 441ec017da
No known key found for this signature in database
6 changed files with 68 additions and 6 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- **provider**: Optional `custom_headers` map to send extra HTTP headers on every LiteLLM API request (e.g. proxy or gateway headers). `x-api-key` from `api_key` always takes precedence
## [0.4.0] - 2026-08-06
### Fixed

View file

@ -16,6 +16,11 @@ terraform {
provider "litellm" {
api_base = "https://your-litellm-proxy.com"
api_key = var.litellm_api_key
# Optional: extra headers on every API request (e.g. proxy / gateway auth)
custom_headers = {
"X-Proxy-Auth" = var.proxy_token
}
}
# Basic model configuration
@ -94,6 +99,8 @@ The following arguments are supported in the provider block:
* `api_base` - (Required) The base URL of your LiteLLM instance. This can also be provided via the `LITELLM_API_BASE` environment variable.
* `api_key` - (Required) The API key used to authenticate with LiteLLM. This can also be provided via the `LITELLM_API_KEY` environment variable.
* `custom_headers` - (Optional) Map of HTTP headers to include on every request to the LiteLLM API. Useful for proxy or gateway authentication headers. Marked sensitive.
* `insecure_skip_verify` - (Optional) Skip TLS certificate verification. Defaults to `false`. Can also be set via `LITELLM_INSECURE_SKIP_VERIFY`.
## Getting Started

View file

@ -17,6 +17,7 @@ type Client struct {
APIKey string
httpClient *http.Client
InsecureSkipVerify bool
CustomHeaders map[string]string
}
func NewClient(apiBase, apiKey string, insecureSkipVerify bool) *Client {
@ -29,6 +30,7 @@ func NewClient(apiBase, apiKey string, insecureSkipVerify bool) *Client {
APIKey: apiKey,
httpClient: &http.Client{Transport: tr},
InsecureSkipVerify: insecureSkipVerify,
CustomHeaders: map[string]string{},
}
}
@ -280,8 +282,11 @@ func (c *Client) sendRequest(method, path string, body interface{}) (map[string]
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", c.APIKey)
req.Header.Set("accept", "application/json")
for k, v := range c.CustomHeaders {
req.Header.Set(k, v)
}
req.Header.Set("x-api-key", c.APIKey)
resp, err := c.httpClient.Do(req)
if err != nil {

View file

@ -1,10 +1,41 @@
package litellm
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestSendRequestCustomHeaders(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("X-Proxy-Auth"); got != "proxy-token" {
t.Errorf("X-Proxy-Auth = %q, want %q", got, "proxy-token")
}
if got := r.Header.Get("X-Request-Source"); got != "terraform" {
t.Errorf("X-Request-Source = %q, want %q", got, "terraform")
}
if got := r.Header.Get("x-api-key"); got != "sk-real-key" {
t.Errorf("x-api-key = %q, want configured API key (must not be overridden)", got)
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true})
}))
defer srv.Close()
client := NewClient(srv.URL, "sk-real-key", false)
client.CustomHeaders = map[string]string{
"X-Proxy-Auth": "proxy-token",
"X-Request-Source": "terraform",
"x-api-key": "should-not-win",
}
if _, err := client.sendRequest("GET", "/health", nil); err != nil {
t.Fatalf("sendRequest: %v", err)
}
}
func TestRedactSensitiveDataNestedCredentialValues(t *testing.T) {
c := NewClient("http://localhost:4000", "sk-test", false)

View file

@ -46,6 +46,13 @@ func Provider() *schema.Provider {
DefaultFunc: schema.EnvDefaultFunc("LITELLM_INSECURE_SKIP_VERIFY", false),
Description: "Skip TLS certificate verification. Only use for development or when using self-signed certificates",
},
"custom_headers": {
Type: schema.TypeMap,
Optional: true,
Sensitive: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "Optional HTTP headers to include on every request to the LiteLLM API (e.g. proxy or gateway headers)",
},
},
ConfigureFunc: providerConfigure,
}
@ -53,11 +60,18 @@ func Provider() *schema.Provider {
// providerConfigure configures the provider with the given schema data.
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := ProviderConfig{
APIBase: d.Get("api_base").(string),
APIKey: d.Get("api_key").(string),
InsecureSkipVerify: d.Get("insecure_skip_verify").(bool),
customHeaders := map[string]string{}
if v, ok := d.GetOk("custom_headers"); ok {
for k, val := range v.(map[string]interface{}) {
customHeaders[k] = val.(string)
}
}
return NewClient(config.APIBase, config.APIKey, config.InsecureSkipVerify), nil
client := NewClient(
d.Get("api_base").(string),
d.Get("api_key").(string),
d.Get("insecure_skip_verify").(bool),
)
client.CustomHeaders = customHeaders
return client, nil
}

View file

@ -5,6 +5,7 @@ type ProviderConfig struct {
APIBase string
APIKey string
InsecureSkipVerify bool
CustomHeaders map[string]string
}
// ErrorResponse represents an error response from the API.