Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from kubeflow:main #158

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a4e3600
build: use also different K8s versions for E2E testing in GHA (#659)
tarilabs Dec 18, 2024
4867116
feat(bff): list user namespaces in dev mode (#644)
ederign Dec 19, 2024
9f68ddc
feat(bff): authorize endpoints based on kubeflow-userid and kubeflow-…
ederign Dec 19, 2024
62f6707
fix(frontend): fixing cve (low severity), build(deps): bump sass from…
ederign Dec 20, 2024
7249588
Wrap up kubeflow deployment (#662)
lucferbux Dec 20, 2024
239a8b7
build(deps-dev): bump ruff from 0.8.3 to 0.8.4 in /clients/python (#663)
dependabot[bot] Dec 23, 2024
3cdee3f
build(deps-dev): bump mypy from 1.13.0 to 1.14.0 in /clients/python (…
dependabot[bot] Dec 23, 2024
22af5f1
build(deps): bump helm/kind-action from 1.11.0 to 1.12.0 (#665)
dependabot[bot] Dec 23, 2024
4983893
build(deps): bump aiohttp from 3.11.10 to 3.11.11 in /clients/python …
dependabot[bot] Dec 23, 2024
928e5e8
build(deps): bump pydantic from 2.10.3 to 2.10.4 in /clients/python (…
dependabot[bot] Dec 23, 2024
b5113f4
build(deps): bump huggingface-hub from 0.26.5 to 0.27.0 in /clients/p…
dependabot[bot] Dec 23, 2024
a37c78b
build(deps-dev): bump @testing-library/react from 16.0.1 to 16.1.0 in…
dependabot[bot] Dec 24, 2024
56f18a7
build(deps): bump @emotion/react from 11.13.5 to 11.14.0 in /clients/…
dependabot[bot] Dec 24, 2024
77da9e7
build(deps-dev): bump style-loader from 2.0.0 to 4.0.0 in /clients/ui…
dependabot[bot] Dec 24, 2024
1282f98
build(deps-dev): bump @mui/types from 7.2.19 to 7.2.20 in /clients/ui…
dependabot[bot] Dec 24, 2024
3388cc6
build(deps-dev): bump jinja2 from 3.1.4 to 3.1.5 in /clients/python (…
dependabot[bot] Dec 24, 2024
fb21e62
build(deps): bump @patternfly/react-core from 6.0.0 to 6.1.0 in /clie…
dependabot[bot] Dec 30, 2024
5113434
build(deps): bump @patternfly/react-table from 6.0.0 to 6.1.0 in /cli…
dependabot[bot] Dec 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: kubeflow
Expand Down Expand Up @@ -122,10 +135,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: "${{ 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 @@ -26,7 +26,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
Loading