Skip to content

Commit

Permalink
Rename header, add to docs/comments
Browse files Browse the repository at this point in the history
  • Loading branch information
nstogner committed Oct 23, 2024
1 parent df2a21d commit c0ab44b
Show file tree
Hide file tree
Showing 8 changed files with 15 additions and 11 deletions.
Binary file modified docs/diagrams/multitenancy-labels.excalidraw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 6 additions & 4 deletions docs/how-to/architect-for-multitenancy.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architect for Multitenancy

KubeAI can support multitenancy by filtering the models that it serves via Kubernetes [label selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors). These label selectors can be specified on all OpenAI-compatible endpoints through the `X-Selector` HTTP header and will match on labels specified on the `kind: Model` objects. The pattern is similar to using a `WHERE` clause in a SQL query.
KubeAI can support multitenancy by filtering the models that it serves via Kubernetes [label selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors). These label selectors can be specified on all OpenAI-compatible endpoints through the `X-Label-Selector` HTTP header and will match on labels specified on the `kind: Model` objects. The pattern is similar to using a `WHERE` clause in a SQL query.

Example Models:

Expand All @@ -27,16 +27,18 @@ Example HTTP requests:
```bash
# List of models will be filtered.
curl http://$KUBEAI_ENDPOINT/openai/v1/models \
-H "X-Selector: tenancy in (org-abc, public)"
-H "X-Label-Selector: tenancy in (org-abc, public)"

# When running inference, if the label selector does not match
# a 404 will be returned.
curl http://$KUBEAI_ENDPOINT/openai/v1/completions \
-H "Content-Type: application/json" \
-H "X-Selector: tenancy in (org-abc, public)" \
-H "X-Label-Selector: tenancy in (org-abc, public)" \
-d '{"prompt": "Hi", "model": "llama-3.2"}'
```

Example architecture:

![Multitenancy](../diagrams/multitenancy-labels.excalidraw.png)
![Multitenancy](../diagrams/multitenancy-labels.excalidraw.png)

NOTE: Multiple `X-Label-Selector` headers can be specified in the same HTTP request and will be treated as a logical `AND`.
2 changes: 1 addition & 1 deletion internal/modelproxy/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func newProxyRequest(r *http.Request) *proxyRequest {
// attempts to unmarshal the request body as JSON and extract the
// .model field.
func (pr *proxyRequest) parse() error {
pr.selectors = pr.r.Header.Values("X-Selector")
pr.selectors = pr.r.Header.Values("X-Label-Selector")

// Try to get the model from the header first
if headerModel := pr.r.Header.Get("X-Model"); headerModel != "" {
Expand Down
2 changes: 1 addition & 1 deletion internal/openaiserver/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (h *Handler) getModels(w http.ResponseWriter, r *http.Request) {
}

var listOpts []client.ListOption
headerSelectors := r.Header.Values("X-Selector")
headerSelectors := r.Header.Values("X-Label-Selector")
for _, sel := range headerSelectors {
parsedSel, err := labels.Parse(sel)
if err != nil {
Expand Down
Binary file modified proposals/diagrams/auth-with-label-selector.excalidraw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions proposals/auth.md → proposals/multitenancy.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Auth
# Multitenancy

The goal of this proposal is to allow KubeAI to be used in a multitenancy environment where
some users only have access to some models.
Expand Down Expand Up @@ -46,10 +46,10 @@ In this implementation, label selectors are used to filter models. The decision
```bash
curl http://localhost:8000/openai/v1/completions \
-H "X-Selector: key1=value1"
-H "X-Label-Selector: key1=value1"

curl http://localhost:8000/openai/v1/models \
-H "X-Selector: key1=value1"
-H "X-Label-Selector: key1=value1"
```
Models just need to have the labels set.
Expand Down
2 changes: 2 additions & 0 deletions test/integration/selector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ func TestSelector(t *testing.T) {
expCode: http.StatusOK,
},
{
// `AND` logic should be used.
// This is important because if `OR` logic were used it would open up a possible vulerability: if the headers that an end-user specified were proxied with `OR` logic it would allow users to circumvent and proxy-enforced selectors.
name: "model exists 2/2 labels match separate headers",
modelName: m0.Name,
selectorHeaders: []string{
Expand Down
4 changes: 2 additions & 2 deletions test/integration/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func sendOpenAIInferenceRequest(t *testing.T, modelName string, selectorHeaders
require.NoError(t, err, msg)
for _, selector := range selectorHeaders {
t.Logf("Using selector: %s", selector)
req.Header.Add("X-Selector", selector)
req.Header.Add("X-Label-Selector", selector)
}

res, err := testHTTPClient.Do(req)
Expand All @@ -174,7 +174,7 @@ func sendOpenAIListModelsRequest(t *testing.T, selectorHeaders []string, expCode
req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/openai/v1/models", nil)
require.NoError(t, err, msg)
for _, selector := range selectorHeaders {
req.Header.Add("X-Selector", selector)
req.Header.Add("X-Label-Selector", selector)
}

res, err := testHTTPClient.Do(req)
Expand Down

0 comments on commit c0ab44b

Please sign in to comment.