Skip to content

Commit

Permalink
Merge pull request #158 from kubeflow/main
Browse files Browse the repository at this point in the history
[pull] main from kubeflow:main
  • Loading branch information
openshift-merge-bot[bot] authored Jan 7, 2025
2 parents 92cafe9 + 2a39432 commit 4d561ba
Show file tree
Hide file tree
Showing 73 changed files with 1,632 additions and 683 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-image-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
IMG_VERSION: ${{ steps.tags.outputs.tag }}
run: make image/build
- name: Start Kind Cluster
uses: helm/kind-action@v1.11.0
uses: helm/kind-action@v1.12.0
with:
node_image: "kindest/node:v1.27.11"
- name: Load Local Registry Test Image
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/csi-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
run: make image/build

- name: Start KinD cluster
uses: helm/kind-action@v1.11.0
uses: helm/kind-action@v1.12.0
with:
node_image: "kindest/node:v1.27.11"

Expand Down
22 changes: 18 additions & 4 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,25 @@ jobs:
fi
test:
name: Test against Py ${{ matrix.python }}
name: Test against Py ${{ matrix.python }} and K8s ${{ matrix.kubernetes-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: ["3.12", "3.11", "3.10", "3.9"]
python: ["3.12"] # see below for versions 3.9-3.11
kubernetes-version: ["v1.27.11", "v1.28.7", "v1.29.2", "v1.30.6", "v1.31.0"]
exclude: # on main merges (not PRs), use also different K8s versions for E2E testing
- kubernetes-version: ${{ github.event_name != 'push' && 'v1.28.7' }}
- kubernetes-version: ${{ github.event_name != 'push' && 'v1.29.2' }}
- kubernetes-version: ${{ github.event_name != 'push' && 'v1.30.6' }}
- kubernetes-version: ${{ github.event_name != 'push' && 'v1.31.0' }}
include: # test Py versions only with a reference K8s version, designated currently to kubernetes-version: v1.27.11
- python: "3.11"
kubernetes-version: "v1.27.11"
- python: "3.10"
kubernetes-version: "v1.27.11"
- python: "3.9"
kubernetes-version: "v1.27.11"
env:
FORCE_COLOR: "1"
IMG_ORG: opendatahub
Expand Down Expand Up @@ -123,10 +136,11 @@ jobs:
IMG_VERSION: ${{ steps.tags.outputs.tag }}
run: make image/build
- name: Start Kind Cluster
uses: helm/kind-action@v1.11.0
uses: helm/kind-action@v1.12.0
with:
node_image: "kindest/node:v1.27.11"
node_image: kindest/node:${{ matrix.kubernetes-version }}
cluster_name: chart-testing-py-${{ matrix.python }}
kubectl_version: ${{ matrix.kubernetes-version }}
- name: Load Local Registry Test Image
env:
IMG: "quay.io/${{ env.IMG_ORG }}/${{ env.IMG_REPO }}:${{ steps.tags.outputs.tag }}"
Expand Down
486 changes: 243 additions & 243 deletions clients/python/poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion clients/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ nest-asyncio = "^1.6.0"
# necessary for modern type annotations using pydantic on 3.9
eval-type-backport = "^0.2.0"

huggingface-hub = { version = ">=0.20.1,<0.27.0", optional = true }
huggingface-hub = { version = ">=0.20.1,<0.28.0", optional = true }

[tool.poetry.extras]
hf = ["huggingface-hub"]
Expand Down
3 changes: 3 additions & 0 deletions clients/ui/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
APP_ENV=development
MOCK_AUTH=true
DEPLOYMENT_MODE=standalone
1 change: 1 addition & 0 deletions clients/ui/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
APP_ENV=production
2 changes: 1 addition & 1 deletion clients/ui/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dev-install-dependencies:

.PHONY: dev-bff
dev-bff:
cd bff && make run PORT=4000 MOCK_K8S_CLIENT=true MOCK_MR_CLIENT=true
cd bff && make run PORT=4000 MOCK_K8S_CLIENT=true MOCK_MR_CLIENT=true DEV_MODE=true STANDALONE_MODE=true

.PHONY: dev-frontend
dev-frontend:
Expand Down
3 changes: 2 additions & 1 deletion clients/ui/bff/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ MOCK_K8S_CLIENT ?= false
MOCK_MR_CLIENT ?= false
DEV_MODE ?= false
DEV_MODE_PORT ?= 8080
STANDALONE_MODE ?= true
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.29.0

Expand Down Expand Up @@ -47,7 +48,7 @@ build: fmt vet test ## Builds the project to produce a binary executable.
.PHONY: run
run: fmt vet envtest ## Runs the project.
ENVTEST_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" \
go run ./cmd/main.go --port=$(PORT) --mock-k8s-client=$(MOCK_K8S_CLIENT) --mock-mr-client=$(MOCK_MR_CLIENT) --dev-mode=$(DEV_MODE) --dev-mode-port=$(DEV_MODE_PORT)
go run ./cmd/main.go --port=$(PORT) --mock-k8s-client=$(MOCK_K8S_CLIENT) --mock-mr-client=$(MOCK_MR_CLIENT) --dev-mode=$(DEV_MODE) --dev-mode-port=$(DEV_MODE_PORT) --standalone-mode=$(STANDALONE_MODE)

.PHONY: docker-build
docker-build: ## Builds a container for the project.
Expand Down
84 changes: 65 additions & 19 deletions clients/ui/bff/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ make docker-build
|----------------------------------------------------------------------------------------------|----------------------------------------------|-------------------------------------------------------------|
| GET /v1/healthcheck | HealthcheckHandler | Show application information. |
| GET /v1/user | UserHandler | Show "kubeflow-user-id" from header information. |
| GET /v1/namespaces | NamespacesHandler | Get all user namespaces. (only enabled in devmode) |
| GET /v1/model_registry | ModelRegistryHandler | Get all model registries, |
| GET /v1/model_registry/{model_registry_id}/registered_models | GetAllRegisteredModelsHandler | Gets a list of all RegisteredModel entities. |
| POST /v1/model_registry/{model_registry_id}/registered_models | CreateRegisteredModelHandler | Create a RegisteredModel entity. |
Expand All @@ -71,28 +72,51 @@ make docker-build
| GET /api/v1/model_registry/{model_registry_id}/model_versions/{model_version_id}/artifacts | GetAllModelArtifactsByModelVersionHandler | Get all ModelArtifact entities by ModelVersion ID |
| POST /api/v1/model_registry/{model_registry_id}/model_versions/{model_version_id}/artifacts | CreateModelArtifactByModelVersion | Create a ModelArtifact entity for a specific ModelVersion |

Note: Most API paths require the namespace parameter to be passed as a query parameter.
The only exceptions are the health check (/v1/healthcheck) and user (/v1/user) paths, which do not require the namespace parameter.

### Sample local calls

You will need to inject your requests with a kubeflow-userid header for authorization purposes. When running the service with the mocked Kubernetes client (MOCK_K8S_CLIENT=true), the user [email protected] is preconfigured with the necessary RBAC permissions to perform these actions.
You will need to inject your requests with a `kubeflow-userid` header and namespace for authorization purposes.

When running the service with the mocked Kubernetes client (MOCK_K8S_CLIENT=true), the user `[email protected]` is preconfigured with the necessary RBAC permissions to perform these actions.
```
# GET /v1/healthcheck
curl -i -H "kubeflow-userid: [email protected]" localhost:4000/api/v1/healthcheck
curl -i -H "kubeflow-userid: [email protected]" "localhost:4000/api/v1/healthcheck"
```
```
# GET /v1/user
curl -i -H "kubeflow-userid: [email protected]" localhost:4000/api/v1/user
curl -i -H "kubeflow-userid: [email protected]" "localhost:4000/api/v1/user"
```
```
# GET /v1/namespaces (only works when DEV_MODE=true)
curl -i -H "kubeflow-userid: [email protected]" "localhost:4000/api/v1/namespaces"
```
```
# GET /v1/model_registry
curl -i -H "kubeflow-userid: [email protected]" localhost:4000/api/v1/model_registry
curl -i -H "kubeflow-userid: [email protected]" "localhost:4000/api/v1/model_registry?namespace=kubeflow"
```
```
# GET /v1/model_registry using groups permissions
curl -i \
-H "kubeflow-userid: [email protected]" \
-H "kubeflow-groups: dora-namespace-group ,group2,group3" \
"http://localhost:4000/api/v1/model_registry?namespace=dora-namespace"
```
```
# GET /v1/model_registry/{model_registry_id}/registered_models
curl -i -H "kubeflow-userid: [email protected]" localhost:4000/api/v1/model_registry/model-registry/registered_models
curl -i -H "kubeflow-userid: [email protected]" "localhost:4000/api/v1/model_registry/model-registry/registered_models?namespace=kubeflow"
```
```
# GET /v1/model_registry/{model_registry_id}/registered_models using group permissions
curl -i \
-H "kubeflow-userid: [email protected]" \
-H "kubeflow-groups: dora-namespace-group ,dora-service-group,group3" \
"http://localhost:4000/api/v1/model_registry/model-registry-dora/registered_models?namespace=dora-namespace"
```
```
#POST /v1/model_registry/{model_registry_id}/registered_models
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/registered_models" \
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/registered_models?namespace=kubeflow" \
-H "Content-Type: application/json" \
-d '{ "data": {
"customProperties": {
Expand All @@ -110,23 +134,23 @@ curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/ap
```
```
# GET /v1/model_registry/{model_registry_id}/registered_models/{registered_model_id}
curl -i -H "kubeflow-userid: [email protected]" localhost:4000/api/v1/model_registry/model-registry/registered_models/1
curl -i -H "kubeflow-userid: [email protected]" "localhost:4000/api/v1/model_registry/model-registry/registered_models/1?namespace=kubeflow"
```
```
# PATCH /v1/model_registry/{model_registry_id}/registered_models/{registered_model_id}
curl -i -H "kubeflow-userid: [email protected]" -X PATCH "http://localhost:4000/api/v1/model_registry/model-registry/registered_models/1" \
curl -i -H "kubeflow-userid: [email protected]" -X PATCH "http://localhost:4000/api/v1/model_registry/model-registry/registered_models/1?namespace=kubeflow" \
-H "Content-Type: application/json" \
-d '{ "data": {
"description": "New description"
}}'
```
```
# GET /api/v1/model_registry/{model_registry_id}/model_versions/{model_version_id}
curl -i -H "kubeflow-userid: [email protected]" http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1
curl -i -H "kubeflow-userid: [email protected]" "http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1?namespace=kubeflow"
```
```
# POST /api/v1/model_registry/{model_registry_id}/model_versions
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/model_versions" \
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/model_versions?namespace=kubeflow" \
-H "Content-Type: application/json" \
-d '{ "data": {
"customProperties": {
Expand All @@ -145,19 +169,19 @@ curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/ap
```
```
# PATCH /api/v1/model_registry/{model_registry_id}/model_versions/{model_version_id}
curl -i -H "kubeflow-userid: [email protected]" -X PATCH "http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1" \
curl -i -H "kubeflow-userid: [email protected]" -X PATCH "http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1?namespace=kubeflow" \
-H "Content-Type: application/json" \
-d '{ "data": {
"description": "New description 2"
}}'
```
```
# GET /v1/model_registry/{model_registry_id}/registered_models/{registered_model_id}/versions
curl -i -H "kubeflow-userid: [email protected]" localhost:4000/api/v1/model_registry/model-registry/registered_models/1/versions
curl -i -H "kubeflow-userid: [email protected]" "localhost:4000/api/v1/model_registry/model-registry/registered_models/1/versions?namespace=kubeflow"
```
```
# POST /v1/model_registry/{model_registry_id}/registered_models/{registered_model_id}/versions
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/registered_models/1/versions" \
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/registered_models/1/versions?namespace=kubeflow" \
-H "Content-Type: application/json" \
-d '{ "data": {
"customProperties": {
Expand All @@ -171,16 +195,16 @@ curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/ap
"name": "ModelVersion One",
"state": "LIVE",
"author": "alex",
"registeredModelId: "1"
"registeredModelId": "1"
}}'
```
```
# GET /api/v1/model_registry/{model_registry_id}/model_versions/{model_version_id}/artifacts
curl -i -H "kubeflow-userid: [email protected]" http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1/artifacts
# GET /api/v1/model_registry/{model_registry_id}/model_versions/{model_version_id}/artifacts
curl -i -H "kubeflow-userid: [email protected]" "http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1/artifacts?namespace=kubeflow"
```
```
# POST /api/v1/model_registry/{model_registry_id}/model_versions/{model_version_id}/artifacts
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1/artifacts" \
curl -i -H "kubeflow-userid: [email protected]" -X POST "http://localhost:4000/api/v1/model_registry/model-registry/model_versions/1/artifacts?namespace=kubeflow" \
-H "Content-Type: application/json" \
-d '{ "data": {
"customProperties": {
Expand Down Expand Up @@ -241,13 +265,35 @@ The mock Kubernetes environment is activated when the environment variable `MOCK
- **Namespaces**:
- `kubeflow`
- `dora-namespace`
- `bella-namespace`

- **Users**:
- `[email protected]` (has `cluster-admin` privileges)
- `[email protected]` (restricted to the `dora-namespace`)
- `[email protected]` (restricted to the `bella-namespace`)

- **Groups**:
- `dora-service-group` (has access to `model-registry-dora` inside `dora-namespace`)
- `dora-namespace-group` (has access to the `dora-namespace`)

- **Services (Model Registries)**:
- `model-registry`: resides in the `kubeflow` namespace with the label `component: model-registry`.
- `model-registry-one`: resides in the `kubeflow` namespace with the label `component: model-registry`.
- `non-model-registry`: resides in the `kubeflow` namespace *without* the label `component: model-registry`.
- `model-registry-dora`: resides in the `dora-namespace` namespace with the label `component: model-registry`.
- `model-registry-bella`: resides in the `kubeflow` namespace with the label `component: model-registry`.
- `non-model-registry`: resides in the `kubeflow` namespace *without* the label `component: model-registry`.

#### 3. How BFF authorization works for kubeflow-userid and kubeflow-groups?

Authorization is performed using Kubernetes SubjectAccessReview (SAR), which validates user access to resources.

- `kubeflow-userid`: Required header that specifies the user’s email. Access is checked directly for the user via SAR.
- `kubeflow-groups`: Optional header with a comma-separated list of groups. If the user does not have access, SAR checks group permissions using OR logic. If any group has access, the request is authorized.


Access to Model Registry List:
- To list all model registries (/v1/model_registry), we perform a SAR check for get and list verbs on services within the specified namespace.
- If the user or any group has permission to get and list services in the namespace, the request is authorized.

Access to Specific Model Registry Endpoints:
- For other endpoints (e.g., /v1/model_registry/{model_registry_id}/...), we perform a SAR check for get and list verbs on the specific service (identified by model_registry_id) within the namespace.
- If the user or any group has permission to get or list the specific service, the request is authorized.
1 change: 1 addition & 0 deletions clients/ui/bff/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func main() {
flag.BoolVar(&cfg.MockMRClient, "mock-mr-client", false, "Use mock Model Registry client")
flag.BoolVar(&cfg.DevMode, "dev-mode", false, "Use development mode for access to local K8s cluster")
flag.IntVar(&cfg.DevModePort, "dev-mode-port", getEnvAsInt("DEV_MODE_PORT", 8080), "Use port when in development mode")
flag.BoolVar(&cfg.StandaloneMode, "standalone-mode", false, "Use standalone mode for enabling endpoints in standalone mode")
flag.Parse()

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
Expand Down
Loading

0 comments on commit 4d561ba

Please sign in to comment.