diff --git a/terraform/provider/CHANGELOG.md b/terraform/provider/CHANGELOG.md index 7c744f04064..9d486b44f18 100644 --- a/terraform/provider/CHANGELOG.md +++ b/terraform/provider/CHANGELOG.md @@ -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 diff --git a/terraform/provider/docs/index.md b/terraform/provider/docs/index.md index c03071e7ed3..797cc995970 100644 --- a/terraform/provider/docs/index.md +++ b/terraform/provider/docs/index.md @@ -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 diff --git a/terraform/provider/litellm/client.go b/terraform/provider/litellm/client.go index e0aba61477d..afdc27849b9 100644 --- a/terraform/provider/litellm/client.go +++ b/terraform/provider/litellm/client.go @@ -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 { diff --git a/terraform/provider/litellm/client_test.go b/terraform/provider/litellm/client_test.go index 56f76565616..2749cf6e56e 100644 --- a/terraform/provider/litellm/client_test.go +++ b/terraform/provider/litellm/client_test.go @@ -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) diff --git a/terraform/provider/litellm/provider.go b/terraform/provider/litellm/provider.go index 57f9cc24183..a5877ea23e6 100644 --- a/terraform/provider/litellm/provider.go +++ b/terraform/provider/litellm/provider.go @@ -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 } diff --git a/terraform/provider/litellm/types.go b/terraform/provider/litellm/types.go index 069fe4b3e23..5c13b5a9a48 100644 --- a/terraform/provider/litellm/types.go +++ b/terraform/provider/litellm/types.go @@ -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.