From dc3a376fd92cab22f96a21c08b132660fa43a2ea Mon Sep 17 00:00:00 2001 From: Abdul Hameed Date: Thu, 20 Feb 2025 14:04:33 -0500 Subject: [PATCH 1/3] feat: Add Feast Operator RBAC example with Kubernetes Authentication type. Signed-off-by: Abdul Hameed --- examples/README.md | 3 +- examples/operator-rbac/03-uninstall.ipynb | 175 ++++ .../operator-rbac/1-setup-operator-rbac.ipynb | 753 ++++++++++++++ examples/operator-rbac/2-client.ipynb | 918 ++++++++++++++++++ examples/operator-rbac/README.md | 6 + .../operator-rbac/client/feature_store.yaml | 15 + examples/operator-rbac/permissions_apply.py | 21 + ...v1alpha1_featurestore_kubernetes_auth.yaml | 15 +- 8 files changed, 1902 insertions(+), 4 deletions(-) create mode 100644 examples/operator-rbac/03-uninstall.ipynb create mode 100644 examples/operator-rbac/1-setup-operator-rbac.ipynb create mode 100644 examples/operator-rbac/2-client.ipynb create mode 100644 examples/operator-rbac/README.md create mode 100644 examples/operator-rbac/client/feature_store.yaml create mode 100644 examples/operator-rbac/permissions_apply.py diff --git a/examples/README.md b/examples/README.md index ab5288dd5ad..6dac867be43 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,4 +18,5 @@ The following examples illustrate various **Feast** use cases to enhance underst The examples below showcase how to deploy and manage **Feast on Kubernetes** using the **Feast Go Operator**. 1. **[Operator Quickstart](operator-quickstart)**: Demonstrates how to install and use Feast on Kubernetes with the Feast Go Operator. -1. **[Operator Quickstart with Postgres in TLS](operator-postgres-tls-demo)**: Demonstrates installing and configuring Feast with PostgreSQL in TLS mode on Kubernetes using the Feast Go Operator, with an emphasis on volumes and VolumeMounts support. +1. **[Operator Quickstart with Postgres in TLS](operator-postgres-tls-demo)**: Demonstrates installing and configuring Feast with PostgreSQL in TLS mode on Kubernetes using the Feast Go Operator, with an emphasis on volumes and VolumeMounts support. +1. **[Operator RBAC with Kubernetes](operator-rbac)**: Demonstrates the Feast RBAC example on Kubernetes using the Feast Operator. diff --git a/examples/operator-rbac/03-uninstall.ipynb b/examples/operator-rbac/03-uninstall.ipynb new file mode 100644 index 00000000000..f9c794c03f8 --- /dev/null +++ b/examples/operator-rbac/03-uninstall.ipynb @@ -0,0 +1,175 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "## Uninstall", + "id": "bd1a081f3f7f5752" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "### Uninstall the Operator and all Feast related objects##", + "id": "1175f3d6c5ee9bf0" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T19:09:52.349677Z", + "start_time": "2025-03-05T19:09:46.308482Z" + } + }, + "cell_type": "code", + "source": [ + "!kubectl delete -f ../../infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml\n", + "!kubectl delete -f ../../infra/feast-operator/dist/install.yaml" + ], + "id": "f4b4c6fa4a1fe0a8", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "featurestore.feast.dev \"sample-kubernetes-auth\" deleted\r\n", + "namespace \"feast-operator-system\" deleted\r\n", + "customresourcedefinition.apiextensions.k8s.io \"featurestores.feast.dev\" deleted\r\n", + "serviceaccount \"feast-operator-controller-manager\" deleted\r\n", + "role.rbac.authorization.k8s.io \"feast-operator-leader-election-role\" deleted\r\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-featurestore-editor-role\" deleted\r\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-featurestore-viewer-role\" deleted\r\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-manager-role\" deleted\r\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-metrics-auth-role\" deleted\r\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-metrics-reader\" deleted\r\n", + "rolebinding.rbac.authorization.k8s.io \"feast-operator-leader-election-rolebinding\" deleted\r\n", + "clusterrolebinding.rbac.authorization.k8s.io \"feast-operator-manager-rolebinding\" deleted\r\n", + "clusterrolebinding.rbac.authorization.k8s.io \"feast-operator-metrics-auth-rolebinding\" deleted\r\n", + "service \"feast-operator-controller-manager-metrics-service\" deleted\r\n", + "deployment.apps \"feast-operator-controller-manager\" deleted\r\n" + ] + } + ], + "execution_count": 6 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## Uninstall Client Related Objects", + "id": "2a2aa884aeddfb99" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T19:09:54.655575Z", + "start_time": "2025-03-05T19:09:53.553918Z" + } + }, + "cell_type": "code", + "source": [ + "!echo \"Deleting RoleBindings...\"\n", + "!kubectl delete rolebinding feast-user-rolebinding -n feast --ignore-not-found\n", + "!kubectl delete rolebinding feast-admin-rolebinding -n feast --ignore-not-found\n", + "\n", + "!echo \"Deleting ServiceAccounts...\"\n", + "!kubectl delete serviceaccount feast-user-sa -n feast --ignore-not-found\n", + "!kubectl delete serviceaccount feast-admin-sa -n feast --ignore-not-found\n", + "!kubectl delete serviceaccount feast-unauthorized-user-sa -n feast --ignore-not-found\n" + ], + "id": "6ce30879d64bbd06", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Deleting RoleBindings...\r\n", + "rolebinding.rbac.authorization.k8s.io \"feast-user-rolebinding\" deleted\r\n", + "rolebinding.rbac.authorization.k8s.io \"feast-admin-rolebinding\" deleted\r\n", + "Deleting ServiceAccounts...\r\n", + "serviceaccount \"feast-user-sa\" deleted\r\n", + "serviceaccount \"feast-admin-sa\" deleted\r\n", + "serviceaccount \"feast-unauthorized-user-sa\" deleted\r\n" + ] + } + ], + "execution_count": 7 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Ensure everything has been removed, or is in the process of being terminated.", + "id": "638421caa8ff849e" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T19:09:59.868383Z", + "start_time": "2025-03-05T19:09:59.611048Z" + } + }, + "cell_type": "code", + "source": "!kubectl get all -n feast\n", + "id": "587eb85352a8a353", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No resources found in feast namespace.\r\n" + ] + } + ], + "execution_count": 8 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T19:10:07.846749Z", + "start_time": "2025-03-05T19:10:02.561070Z" + } + }, + "cell_type": "code", + "source": "!kubectl delete namespace feast", + "id": "7a0ce2d9e4a92828", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "namespace \"feast\" deleted\r\n" + ] + } + ], + "execution_count": 9 + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "", + "id": "10707783148c5f8d" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/operator-rbac/1-setup-operator-rbac.ipynb b/examples/operator-rbac/1-setup-operator-rbac.ipynb new file mode 100644 index 00000000000..04e6a7ca510 --- /dev/null +++ b/examples/operator-rbac/1-setup-operator-rbac.ipynb @@ -0,0 +1,753 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Feast Operator with RBAC Configuration\n", + "## Objective\n", + "\n", + "This demo provides a reference implementation of a runbook on how to enable Role-Based Access Control (RBAC) for Feast using the Feast Operator with the Kubernetes authentication type.\n", + "\n", + "The demo steps include deploying the Feast Operator, creating Feast instances with server components (registry, offline store, online store), and Feast client testing locally. The goal is to ensure secure access control for Feast instances deployed by the Feast Operator.\n", + " \n", + "Please read these reference documents for understanding the Feast RBAC framework.\n", + "- [RBAC Architecture](https://docs.feast.dev/v/master/getting-started/architecture/rbac) \n", + "- [RBAC Permission](https://docs.feast.dev/v/master/getting-started/concepts/permission).\n", + "- [RBAC Authorization Manager](https://docs.feast.dev/v/master/getting-started/components/authz_manager)\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Deployment Architecture\n", + "In this notebook, we will deploy a distributed topology of Feast services, which includes:\n", + "\n", + "* `Registry Server`: Handles metadata storage for feature definitions.\n", + "* `Online Store Server`: Uses the `Registry Server` to query metadata and is responsible for low-latency serving of features.\n", + "* `Offline Store Server`: Uses the `Registry Server` to query metadata and provides access to batch data for historical feature retrieval.\n", + "* `Kubernetes` Authentication types for RBAC Configuration for Feast resources.\n", + "* Setting update Feast RBAC based on roles assigned then validating with client example.\n" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Prerequisites\n", + "* Kubernetes Cluster\n", + "* [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) Kubernetes CLI tool." + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Install Prerequisites\n", + "The following commands install and configure all the prerequisites on a MacOS environment. You can find the\n", + "equivalent instructions on the offical documentation pages:\n", + "* Install the `kubectl` cli.\n", + "* Install Kubernetes and Container runtime (e.g. [Colima](https://github.com/abiosoft/colima)).\n", + " * Alternatively, authenticate to an existing Kubernetes or OpenShift cluster.\n", + " \n", + "```bash\n", + "brew install colima kubectl\n", + "colima start -r containerd -k -m 3 -d 100 -c 2 --cpu-type max -a x86_64\n", + "colima list\n", + "```" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:47:55.278200Z", + "start_time": "2025-03-05T18:47:54.932229Z" + } + }, + "cell_type": "code", + "source": [ + "!kubectl create ns feast\n", + "!kubectl config set-context --current --namespace feast" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "namespace/feast created\r\n", + "Context \"kind-kind\" modified.\r\n" + ] + } + ], + "execution_count": 153 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Validate the cluster setup:" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:48:07.240717Z", + "start_time": "2025-03-05T18:48:04.041808Z" + } + }, + "cell_type": "code", + "source": "!kubectl get ns feast", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME STATUS AGE\r\n", + "feast Active 12s\r\n" + ] + } + ], + "execution_count": 154 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Feast Admin Steps:\n", + "Feast Admin or MLOps Engineers may require Kubernetes Cluster Admin roles when working with OpenShift or Kubernetes clusters. Below is the list of steps Required to perform setting up Feast RBAC with Operator by Admin or MLOps Engineers.\n", + "\n", + "1. **Install the Feast Operator**\n", + "2. **Install the Feast services via FeatureStore CR**\n", + "3. **Configure the RBAC Permissions**\n", + "4. **Perform Feast Apply**\n", + "5. **Setting Service Account and Role Binding**\n", + "\n", + "## Install the Feast Operator" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:48:25.617201Z", + "start_time": "2025-03-05T18:48:12.812694Z" + } + }, + "cell_type": "code", + "source": [ + "## Use this install command from a release branch (e.g. 'v0.43-branch')\n", + "!kubectl apply -f ../../infra/feast-operator/dist/install.yaml\n", + "\n", + "## OR, for the latest code/builds, use one the following commands from the 'master' branch\n", + "# !make -C ../../infra/feast-operator install deploy IMG=quay.io/feastdev-ci/feast-operator:develop FS_IMG=quay.io/feastdev-ci/feature-server:develop\n", + "# !make -C ../../infra/feast-operator install deploy IMG=quay.io/feastdev-ci/feast-operator:$(git rev-parse HEAD) FS_IMG=quay.io/feastdev-ci/feature-server:$(git rev-parse HEAD)\n", + "\n", + "!kubectl wait --for=condition=available --timeout=5m deployment/feast-operator-controller-manager -n feast-operator-system" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "namespace/feast-operator-system created\r\n", + "customresourcedefinition.apiextensions.k8s.io/featurestores.feast.dev created\r\n", + "serviceaccount/feast-operator-controller-manager created\r\n", + "role.rbac.authorization.k8s.io/feast-operator-leader-election-role created\r\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-featurestore-editor-role created\r\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-featurestore-viewer-role created\r\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-manager-role created\r\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-metrics-auth-role created\r\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-metrics-reader created\r\n", + "rolebinding.rbac.authorization.k8s.io/feast-operator-leader-election-rolebinding created\r\n", + "clusterrolebinding.rbac.authorization.k8s.io/feast-operator-manager-rolebinding created\r\n", + "clusterrolebinding.rbac.authorization.k8s.io/feast-operator-metrics-auth-rolebinding created\r\n", + "service/feast-operator-controller-manager-metrics-service created\r\n", + "deployment.apps/feast-operator-controller-manager created\r\n", + "deployment.apps/feast-operator-controller-manager condition met\r\n" + ] + } + ], + "execution_count": 155 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install the Feast services via FeatureStore CR\n", + "Next, we'll use the running Feast Operator to install the feast services with Server components online, offline, registry with kubernetes Authorization set. Apply the included [reference deployment](../../infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml) to install and configure Feast with kubernetes Authorization ." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:48:41.993231Z", + "start_time": "2025-03-05T18:48:38.573278Z" + } + }, + "source": [ + "!cat ../../infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml\n", + "!kubectl apply -f ../../infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml -n feast" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "apiVersion: feast.dev/v1alpha1\r\n", + "kind: FeatureStore\r\n", + "metadata:\r\n", + " name: sample-kubernetes-auth\r\n", + "spec:\r\n", + " feastProject: feast_rbac\r\n", + " authz:\r\n", + " kubernetes:\r\n", + " roles:\r\n", + " - feast-writer\r\n", + " - feast-reader\r\n", + " services:\r\n", + " offlineStore:\r\n", + " server: {}\r\n", + " onlineStore:\r\n", + " server: {}\r\n", + " registry:\r\n", + " local:\r\n", + " server: {}\r\n", + " ui: {}\r\n", + "featurestore.feast.dev/sample-kubernetes-auth created\r\n" + ] + } + ], + "execution_count": 156 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Validate the running FeatureStore deployment\n", + "Validate the deployment status." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:49:07.735147Z", + "start_time": "2025-03-05T18:48:47.817817Z" + } + }, + "source": [ + "!kubectl get all\n", + "!kubectl wait --for=condition=available --timeout=8m deployment/feast-sample-kubernetes-auth" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME READY STATUS RESTARTS AGE\r\n", + "pod/feast-sample-kubernetes-auth-774f6df8df-9ngcl 0/4 Init:0/1 0 5s\r\n", + "\r\n", + "NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\r\n", + "service/feast-sample-kubernetes-auth-offline ClusterIP 10.96.2.92 80/TCP 5s\r\n", + "service/feast-sample-kubernetes-auth-online ClusterIP 10.96.128.237 80/TCP 5s\r\n", + "service/feast-sample-kubernetes-auth-registry ClusterIP 10.96.209.35 80/TCP 5s\r\n", + "service/feast-sample-kubernetes-auth-ui ClusterIP 10.96.76.211 80/TCP 5s\r\n", + "\r\n", + "NAME READY UP-TO-DATE AVAILABLE AGE\r\n", + "deployment.apps/feast-sample-kubernetes-auth 0/1 1 0 5s\r\n", + "\r\n", + "NAME DESIRED CURRENT READY AGE\r\n", + "replicaset.apps/feast-sample-kubernetes-auth-774f6df8df 1 1 0 5s\r\n", + "deployment.apps/feast-sample-kubernetes-auth condition met\r\n" + ] + } + ], + "execution_count": 157 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Validate that the FeatureStore CR is in a `Ready` state." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:49:15.695148Z", + "start_time": "2025-03-05T18:49:15.499064Z" + } + }, + "source": [ + "!kubectl get feast" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME STATUS AGE\r\n", + "sample-kubernetes-auth Ready 34s\r\n" + ] + } + ], + "execution_count": 158 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Configure the RBAC Permissions\n", + "we have defined permission in `permissions_apply.py`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:49:43.015317Z", + "start_time": "2025-03-05T18:49:42.826986Z" + } + }, + "cell_type": "code", + "source": [ + "#view the permissions \n", + "!cat permissions_apply.py" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "from feast.feast_object import ALL_RESOURCE_TYPES\r\n", + "from feast.permissions.action import READ, AuthzedAction, ALL_ACTIONS\r\n", + "from feast.permissions.permission import Permission\r\n", + "from feast.permissions.policy import RoleBasedPolicy\r\n", + "\r\n", + "admin_roles = [\"feast-writer\"]\r\n", + "user_roles = [\"feast-reader\"]\r\n", + "\r\n", + "user_perm = Permission(\r\n", + " name=\"feast_user_permission\",\r\n", + " types=ALL_RESOURCE_TYPES,\r\n", + " policy=RoleBasedPolicy(roles=user_roles),\r\n", + " actions=[AuthzedAction.DESCRIBE] + READ\r\n", + ")\r\n", + "\r\n", + "admin_perm = Permission(\r\n", + " name=\"feast_admin_permission\",\r\n", + " types=ALL_RESOURCE_TYPES,\r\n", + " policy=RoleBasedPolicy(roles=admin_roles),\r\n", + " actions=ALL_ACTIONS\r\n", + ")\r\n" + ] + } + ], + "execution_count": 159 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:49:48.693053Z", + "start_time": "2025-03-05T18:49:48.261653Z" + } + }, + "cell_type": "code", + "source": [ + "# Copy the Permissions to the pods under feature_repo directory\n", + "!kubectl cp permissions_apply.py $(kubectl get pods -l 'feast.dev/name=sample-kubernetes-auth' -ojsonpath=\"{.items[*].metadata.name}\"):/feast-data/feast_rbac/feature_repo -c online" + ], + "outputs": [], + "execution_count": 160 + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:49:58.767591Z", + "start_time": "2025-03-05T18:49:55.886459Z" + } + }, + "source": [ + "#view the feature_store.yaml configuration \n", + "!kubectl exec deploy/feast-sample-kubernetes-auth -itc online -- cat feature_store.yaml" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "project: feast_rbac\r\n", + "provider: local\r\n", + "offline_store:\r\n", + " type: dask\r\n", + "online_store:\r\n", + " path: /feast-data/online_store.db\r\n", + " type: sqlite\r\n", + "registry:\r\n", + " path: /feast-data/registry.db\r\n", + " registry_type: file\r\n", + "auth:\r\n", + " type: kubernetes\r\n", + "entity_key_serialization_version: 3\r\n" + ] + } + ], + "execution_count": 161 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## Apply the Permissions and Feast Object to Registry" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:50:15.795804Z", + "start_time": "2025-03-05T18:50:05.431746Z" + } + }, + "cell_type": "code", + "source": "!kubectl exec deploy/feast-sample-kubernetes-auth -itc online -- feast apply", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ": MADV_DONTNEED does not work (memset will be used instead)\r\n", + ": (This is the expected behaviour if you are running under QEMU)\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\r\n", + " DUMMY_ENTITY = Entity(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_enabled\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_len\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "/feast-data/feast_rbac/feature_repo/example_repo.py:27: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\r\n", + " driver = Entity(name=\"driver\", join_keys=[\"driver_id\"])\r\n", + "Applying changes for project feast_rbac\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_store.py:579: RuntimeWarning: On demand feature view is an experimental feature. This API is stable, but the functionality does not scale well for offline retrieval\r\n", + " warnings.warn(\r\n", + "Created project \u001B[1m\u001B[32mfeast_rbac\u001B[0m\r\n", + "Created entity \u001B[1m\u001B[32mdriver\u001B[0m\r\n", + "Created feature view \u001B[1m\u001B[32mdriver_hourly_stats_fresh\u001B[0m\r\n", + "Created feature view \u001B[1m\u001B[32mdriver_hourly_stats\u001B[0m\r\n", + "Created on demand feature view \u001B[1m\u001B[32mtransformed_conv_rate_fresh\u001B[0m\r\n", + "Created on demand feature view \u001B[1m\u001B[32mtransformed_conv_rate\u001B[0m\r\n", + "Created feature service \u001B[1m\u001B[32mdriver_activity_v1\u001B[0m\r\n", + "Created feature service \u001B[1m\u001B[32mdriver_activity_v3\u001B[0m\r\n", + "Created feature service \u001B[1m\u001B[32mdriver_activity_v2\u001B[0m\r\n", + "Created permission \u001B[1m\u001B[32mfeast_admin_permission\u001B[0m\r\n", + "Created permission \u001B[1m\u001B[32mfeast_user_permission\u001B[0m\r\n", + "\r\n", + "Created sqlite table \u001B[1m\u001B[32mfeast_rbac_driver_hourly_stats_fresh\u001B[0m\r\n", + "Created sqlite table \u001B[1m\u001B[32mfeast_rbac_driver_hourly_stats\u001B[0m\r\n", + "\r\n" + ] + } + ], + "execution_count": 162 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "**List the applied permission details permissions on Feast Resources.**" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:51:01.464386Z", + "start_time": "2025-03-05T18:50:19.362018Z" + } + }, + "source": [ + "!kubectl exec deploy/feast-sample-kubernetes-auth -itc online -- feast permissions list-roles\n", + "!kubectl exec deploy/feast-sample-kubernetes-auth -itc online -- feast permissions list\n", + "!kubectl exec deploy/feast-sample-kubernetes-auth -itc online -- feast permissions describe feast_admin_permission\n", + "!kubectl exec deploy/feast-sample-kubernetes-auth -itc online -- feast permissions describe feast_user_permission" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ": MADV_DONTNEED does not work (memset will be used instead)\r\n", + ": (This is the expected behaviour if you are running under QEMU)\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\r\n", + " DUMMY_ENTITY = Entity(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_enabled\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_len\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "+--------------+\r\n", + "| ROLE NAME |\r\n", + "+==============+\r\n", + "| feast-reader |\r\n", + "+--------------+\r\n", + "| feast-writer |\r\n", + "+--------------+\r\n", + ": MADV_DONTNEED does not work (memset will be used instead)\r\n", + ": (This is the expected behaviour if you are running under QEMU)\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\r\n", + " DUMMY_ENTITY = Entity(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_enabled\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_len\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "NAME TYPES NAME_PATTERNS ACTIONS ROLES REQUIRED_TAGS\r\n", + "feast_admin_permission Project - CREATE feast-writer -\r\n", + " FeatureView DESCRIBE\r\n", + " OnDemandFeatureView UPDATE\r\n", + " BatchFeatureView DELETE\r\n", + " StreamFeatureView READ_ONLINE\r\n", + " Entity READ_OFFLINE\r\n", + " FeatureService WRITE_ONLINE\r\n", + " DataSource WRITE_OFFLINE\r\n", + " ValidationReference\r\n", + " SavedDataset\r\n", + " Permission\r\n", + "feast_user_permission Project - DESCRIBE feast-reader -\r\n", + " FeatureView READ_OFFLINE\r\n", + " OnDemandFeatureView READ_ONLINE\r\n", + " BatchFeatureView\r\n", + " StreamFeatureView\r\n", + " Entity\r\n", + " FeatureService\r\n", + " DataSource\r\n", + " ValidationReference\r\n", + " SavedDataset\r\n", + " Permission\r\n", + ": MADV_DONTNEED does not work (memset will be used instead)\r\n", + ": (This is the expected behaviour if you are running under QEMU)\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\r\n", + " DUMMY_ENTITY = Entity(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_enabled\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_len\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "spec:\r\n", + " name: feast_admin_permission\r\n", + " types:\r\n", + " - PROJECT\r\n", + " - FEATURE_VIEW\r\n", + " - ON_DEMAND_FEATURE_VIEW\r\n", + " - BATCH_FEATURE_VIEW\r\n", + " - STREAM_FEATURE_VIEW\r\n", + " - ENTITY\r\n", + " - FEATURE_SERVICE\r\n", + " - DATA_SOURCE\r\n", + " - VALIDATION_REFERENCE\r\n", + " - SAVED_DATASET\r\n", + " - PERMISSION\r\n", + " actions:\r\n", + " - CREATE\r\n", + " - DESCRIBE\r\n", + " - UPDATE\r\n", + " - DELETE\r\n", + " - READ_ONLINE\r\n", + " - READ_OFFLINE\r\n", + " - WRITE_ONLINE\r\n", + " - WRITE_OFFLINE\r\n", + " policy:\r\n", + " roleBasedPolicy:\r\n", + " roles:\r\n", + " - feast-writer\r\n", + "meta:\r\n", + " createdTimestamp: '2025-03-05T18:50:15.487200Z'\r\n", + " lastUpdatedTimestamp: '2025-03-05T18:50:15.487200Z'\r\n", + "\r\n", + ": MADV_DONTNEED does not work (memset will be used instead)\r\n", + ": (This is the expected behaviour if you are running under QEMU)\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\r\n", + " DUMMY_ENTITY = Entity(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_enabled\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "/opt/app-root/lib64/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_len\" in \"SqliteOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\r\n", + " warnings.warn(\r\n", + "spec:\r\n", + " name: feast_user_permission\r\n", + " types:\r\n", + " - PROJECT\r\n", + " - FEATURE_VIEW\r\n", + " - ON_DEMAND_FEATURE_VIEW\r\n", + " - BATCH_FEATURE_VIEW\r\n", + " - STREAM_FEATURE_VIEW\r\n", + " - ENTITY\r\n", + " - FEATURE_SERVICE\r\n", + " - DATA_SOURCE\r\n", + " - VALIDATION_REFERENCE\r\n", + " - SAVED_DATASET\r\n", + " - PERMISSION\r\n", + " actions:\r\n", + " - DESCRIBE\r\n", + " - READ_OFFLINE\r\n", + " - READ_ONLINE\r\n", + " policy:\r\n", + " roleBasedPolicy:\r\n", + " roles:\r\n", + " - feast-reader\r\n", + "meta:\r\n", + " createdTimestamp: '2025-03-05T18:50:15.487974Z'\r\n", + " lastUpdatedTimestamp: '2025-03-05T18:50:15.487974Z'\r\n", + "\r\n" + ] + } + ], + "execution_count": 163 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Setting Up Service Account and RoleBinding \n", + "The steps below will:\n", + "- Create **three different ServiceAccounts** for Feast.\n", + "- Assign appropriate **RoleBindings** for access control." + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Test Cases\n", + "| User Type | ServiceAccount | RoleBinding Assigned | Expected Behavior in output |\n", + "|----------------|-----------------------------|----------------------|------------------------------------------------------------|\n", + "| **Read-Only** | `feast-user-sa` | `feast-reader` | Can **read** from the feature store, but **cannot write**. |\n", + "| **Unauthorized** | `feast-unauthorized-user-sa` | _None_ | **Access should be denied** in `test.py`. |\n", + "| **Admin** | `feast-admin-sa` | `feast-writer` | Can **read and write** feature store data. |" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "### Setup Read-Only Feast User the ServiceAccount and Role Binding (serviceaccount: feast-user-sa, role: feast-reader)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:51:17.394789Z", + "start_time": "2025-03-05T18:51:16.725533Z" + } + }, + "cell_type": "code", + "source": [ + "# Step 1: Create the ServiceAccount\n", + "!echo \"Creating ServiceAccount: feast-user-sa\"\n", + "!kubectl create serviceaccount feast-user-sa -n feast\n", + "\n", + "# Step 2: Assign RoleBinding (Read-Only Access for Feast)\n", + "!echo \"Assigning Read-Only RoleBinding: feast-user-rolebinding\"\n", + "!kubectl create rolebinding feast-user-rolebinding --role=feast-reader --serviceaccount=feast:feast-user-sa -n feast" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Creating ServiceAccount: feast-user-sa\r\n", + "serviceaccount/feast-user-sa created\r\n", + "Assigning Read-Only RoleBinding: feast-user-rolebinding\r\n", + "rolebinding.rbac.authorization.k8s.io/feast-user-rolebinding created\r\n" + ] + } + ], + "execution_count": 164 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "### Setup Unauthorized Feast User (serviceaccount: feast-unauthorized-user-sa, role: None)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:51:23.958145Z", + "start_time": "2025-03-05T18:51:23.614975Z" + } + }, + "cell_type": "code", + "source": [ + "# Create the ServiceAccount (Without RoleBinding)\n", + "!echo \"Creating Unauthorized ServiceAccount: feast-unauthorized-user-sa\"\n", + "!kubectl create serviceaccount feast-unauthorized-user-sa -n feast\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Creating Unauthorized ServiceAccount: feast-unauthorized-user-sa\r\n", + "serviceaccount/feast-unauthorized-user-sa created\r\n" + ] + } + ], + "execution_count": 165 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## Setup Test Admin Feast User (serviceaccount: feast-admin-sa, role: feast-writer)" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:51:30.716036Z", + "start_time": "2025-03-05T18:51:30.057571Z" + } + }, + "cell_type": "code", + "source": [ + "# Create the ServiceAccount\n", + "!echo \"Creating ServiceAccount: feast-admin-sa\"\n", + "!kubectl create serviceaccount feast-admin-sa -n feast\n", + "\n", + "# Assign RoleBinding (Admin Access for Feast)\n", + "!echo \"Assigning Admin RoleBinding: feast-admin-rolebinding\"\n", + "!kubectl create rolebinding feast-admin-rolebinding --role=feast-writer --serviceaccount=feast:feast-admin-sa -n feast\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Creating ServiceAccount: feast-admin-sa\r\n", + "serviceaccount/feast-admin-sa created\r\n", + "Assigning Admin RoleBinding: feast-admin-rolebinding\r\n", + "rolebinding.rbac.authorization.k8s.io/feast-admin-rolebinding created\r\n" + ] + } + ], + "execution_count": 166 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## Next Run Client notebook -> 2-client.ipynb" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/operator-rbac/2-client.ipynb b/examples/operator-rbac/2-client.ipynb new file mode 100644 index 00000000000..b99157f199b --- /dev/null +++ b/examples/operator-rbac/2-client.ipynb @@ -0,0 +1,918 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Feast Client with RBAC\n", + "### RBAC Kubernetes Authentication\n", + "This Feast **Role-Based Access Control (RBAC)** in Kubernetes support authentication **inside a Kubernetes pod** and **outside a pod** when running a local script.\n", + "### Inside a Kubernetes Pod\n", + "Feast automatically retrieves the Kubernetes ServiceAccount token from:\n", + "```\n", + "/var/run/secrets/kubernetes.io/serviceaccount/token\n", + "```\n", + "This means:\n", + "- No manual configuration is needed inside a pod.\n", + "- The token is mounted automatically and used for authentication.\n", + "- Developer just need create the binding with role and service account accordingly.\n", + "- Code Reference: \n", + "[Feast Kubernetes Auth Client Manager (Pod Token Usage)](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/permissions/client/kubernetes_auth_client_manager.py#L15) \n", + "- See the example will use service account from pod [Example](https://github.com/feast-dev/feast/blob/master/examples/rbac-remote/client/k8s/)\n", + "\n", + "### Outside a Kubernetes Pod (Local Machine)\n", + "If running Feast outside of Kubernetes, authentication requires setting the token manually:\n", + "```sh\n", + "export LOCAL_K8S_TOKEN=\"your-service-account-token\"\n", + "```\n", + "Feast will use this token for authentication.\n", + "\n", + "Reference: \n", + "[Feast Authentication via `LOCAL_K8S_TOKEN`](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/permissions/client/kubernetes_auth_client_manager.py#L50)" + ], + "id": "bb0145c9c1f6ebcc" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Test Cases\n", + "| User Type | ServiceAccount | RoleBinding Assigned | Expected Behavior in output |\n", + "|----------------|-----------------------------|----------------------|------------------------------------------------------------|\n", + "| **Read-Only** | `feast-user-sa` | `feast-reader` | Can **read** from the feature store, but **cannot write**. |\n", + "| **Unauthorized** | `feast-unauthorized-user-sa` | _None_ | **Access should be denied** in `test.py`. |\n", + "| **Admin** | `feast-admin-sa` | `feast-writer` | Can **read and write** feature store data. |" + ], + "id": "160681ba4ab3c2c5" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "### Feature Store settings", + "id": "6590c081efb1fe3c" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:51:44.898752Z", + "start_time": "2025-03-05T18:51:44.757430Z" + } + }, + "cell_type": "code", + "source": "!cat client/feature_store.yaml", + "id": "fac5f67ff391b5cf", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "project: feast_rbac\r\n", + "provider: local\r\n", + "offline_store:\r\n", + " host: localhost\r\n", + " type: remote\r\n", + " port: 8081\r\n", + "online_store:\r\n", + " path: http://localhost:8082\r\n", + " type: remote\r\n", + "registry:\r\n", + " path: localhost:8083\r\n", + " registry_type: remote\r\n", + "auth:\r\n", + " type: kubernetes\r\n", + "entity_key_serialization_version: 3\r\n" + ] + } + ], + "execution_count": 7 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "**The Operator creates the client ConfigMap containing the feature_store.yaml. We can retrieve it and port froward to local**", + "id": "84f73e09711bff9f" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:51:50.571415Z", + "start_time": "2025-03-05T18:51:50.339196Z" + } + }, + "cell_type": "code", + "source": "!kubectl get configmap feast-sample-kubernetes-auth-client -n feast -o jsonpath='{.data.feature_store\\.yaml}' ", + "id": "456fb4df46f32380", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "project: feast_rbac\r\n", + "provider: local\r\n", + "offline_store:\r\n", + " host: feast-sample-kubernetes-auth-offline.feast.svc.cluster.local\r\n", + " type: remote\r\n", + " port: 80\r\n", + "online_store:\r\n", + " path: http://feast-sample-kubernetes-auth-online.feast.svc.cluster.local:80\r\n", + " type: remote\r\n", + "registry:\r\n", + " path: feast-sample-kubernetes-auth-registry.feast.svc.cluster.local:80\r\n", + " registry_type: remote\r\n", + "auth:\r\n", + " type: kubernetes\r\n", + "entity_key_serialization_version: 3\r\n" + ] + } + ], + "execution_count": 8 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "### The function below is executed to support the preparation of client testing.", + "id": "ae61f4dca31f3466" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Run Port Forwarding for All Services for local testing ", + "id": "28636825ae8f676d" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T19:07:49.786503Z", + "start_time": "2025-03-05T19:07:49.754932Z" + } + }, + "cell_type": "code", + "source": [ + "import subprocess\n", + "\n", + "# Define services and their local ports\n", + "services = {\n", + " \"offline_store\": (\"feast-sample-kubernetes-auth-offline\", 8081),\n", + " \"online_store\": (\"feast-sample-kubernetes-auth-online\", 8082),\n", + " \"registry\": (\"feast-sample-kubernetes-auth-registry\", 8083),\n", + "}\n", + "\n", + "# Start port-forwarding for each service\n", + "port_forward_processes = {}\n", + "for name, (service, local_port) in services.items():\n", + " cmd = f\"kubectl port-forward svc/{service} -n feast {local_port}:80\"\n", + " process = subprocess.Popen(cmd, shell=True)\n", + " port_forward_processes[name] = process\n", + " print(f\"Port forwarding {service} -> localhost:{local_port}\")" + ], + "id": "c014248190863e8a", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Port forwarding feast-sample-kubernetes-auth-offline -> localhost:8081\n", + "Port forwarding feast-sample-kubernetes-auth-online -> localhost:8082\n", + "Port forwarding feast-sample-kubernetes-auth-registry -> localhost:8083\n" + ] + } + ], + "execution_count": 19 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Function to retrieve a Kubernetes service account token and set it as an environment variable", + "id": "c0eccef6379f442c" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:52:03.520366Z", + "start_time": "2025-03-05T18:52:03.512357Z" + } + }, + "cell_type": "code", + "source": [ + "import subprocess\n", + "import os\n", + "\n", + "def get_k8s_token(service_account):\n", + " namespace = \"feast\"\n", + "\n", + " if not service_account:\n", + " raise ValueError(\"Service account name is required.\")\n", + "\n", + " result = subprocess.run(\n", + " [\"kubectl\", \"create\", \"token\", service_account, \"-n\", namespace],\n", + " capture_output=True, text=True, check=True\n", + " )\n", + "\n", + " token = result.stdout.strip()\n", + "\n", + " if not token:\n", + " return None # Silently return None if token retrieval fails\n", + "\n", + " os.environ[\"LOCAL_K8S_TOKEN\"] = token\n", + " return \"Token Retrieved: ***** (hidden for security)\"\n" + ], + "id": "70bdbcd7b3fe44", + "outputs": [], + "execution_count": 10 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "**Generating training data. The following test functions were copied from the `test_workflow.py` template but we added `try` blocks to print only \n", + "the relevant error messages, since we expect to receive errors from the permission enforcement modules.**" + ], + "id": "8c9e27ec4ed8ca2c" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:52:09.002273Z", + "start_time": "2025-03-05T18:52:08.996378Z" + } + }, + "cell_type": "code", + "source": [ + "from feast import FeatureStore\n", + "\n", + "def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):\n", + " try:\n", + " entity_df = pd.DataFrame.from_dict(\n", + " {\n", + " \"driver_id\": [1001, 1002, 1003],\n", + " \"event_timestamp\": [\n", + " datetime(2021, 4, 12, 10, 59, 42),\n", + " datetime(2021, 4, 12, 8, 12, 10),\n", + " datetime(2021, 4, 12, 16, 40, 26),\n", + " ],\n", + " \"label_driver_reported_satisfaction\": [1, 5, 3],\n", + " # values we're using for an on-demand transformation\n", + " \"val_to_add\": [1, 2, 3],\n", + " \"val_to_add_2\": [10, 20, 30],\n", + " }\n", + " )\n", + " if for_batch_scoring:\n", + " entity_df[\"event_timestamp\"] = pd.to_datetime(\"now\", utc=True)\n", + "\n", + " training_df = store.get_historical_features(\n", + " entity_df=entity_df,\n", + " features=[\n", + " \"driver_hourly_stats:conv_rate\",\n", + " \"driver_hourly_stats:acc_rate\",\n", + " \"driver_hourly_stats:avg_daily_trips\",\n", + " \"transformed_conv_rate:conv_rate_plus_val1\",\n", + " \"transformed_conv_rate:conv_rate_plus_val2\",\n", + " ],\n", + "\n", + " ).to_df()\n", + " print(training_df.head())\n", + "\n", + " except Exception as e:\n", + " print(f\"An error occurred while fetching historical features: {e}\")\n", + "\n", + "\n", + "def fetch_online_features(store, source: str = \"\"):\n", + " try:\n", + " entity_rows = [\n", + " # {join_key: entity_value}\n", + " {\n", + " \"driver_id\": 1001,\n", + " \"val_to_add\": 1000,\n", + " \"val_to_add_2\": 2000,\n", + " },\n", + " {\n", + " \"driver_id\": 1002,\n", + " \"val_to_add\": 1001,\n", + " \"val_to_add_2\": 2002,\n", + " },\n", + " ]\n", + " if source == \"feature_service\":\n", + " features_to_fetch = store.get_feature_service(\"driver_activity_v1\")\n", + " elif source == \"push\":\n", + " features_to_fetch = store.get_feature_service(\"driver_activity_v3\")\n", + " else:\n", + " features_to_fetch = [\n", + " \"driver_hourly_stats:acc_rate\",\n", + " \"transformed_conv_rate:conv_rate_plus_val1\",\n", + " \"transformed_conv_rate:conv_rate_plus_val2\",\n", + " ]\n", + " returned_features = store.get_online_features(\n", + " features=features_to_fetch,\n", + " entity_rows=entity_rows,\n", + " ).to_dict()\n", + " for key, value in sorted(returned_features.items()):\n", + " print(key, \" : \", value)\n", + "\n", + " except Exception as e:\n", + " print(f\"An error occurred while fetching online features: {e}\")" + ], + "id": "934963c5f6b18930", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### Test Read-Only Feast User \n", + "**Step 1: Set the Token**" + ], + "id": "84e3f83699b8d83" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T18:52:20.736183Z", + "start_time": "2025-03-05T18:52:20.654808Z" + } + }, + "cell_type": "code", + "source": "get_k8s_token(\"feast-user-sa\")", + "id": "f1fe8baa02d27d38", + "outputs": [ + { + "data": { + "text/plain": [ + "'Token Retrieved: ***** (hidden for security)'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 12 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "**Step 2:Test function features from offline, online and materialize_incremental etc**", + "id": "140c909fa8bcc6ab" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-05T19:08:31.257351Z", + "start_time": "2025-03-05T19:08:25.274715Z" + } + }, + "cell_type": "code", + "source": [ + "!echo \"Running Feast RBAC test for Read only User...\"\n", + "from feast.data_source import PushMode\n", + "from feast import FeatureStore\n", + "from datetime import datetime\n", + "import pandas as pd\n", + "\n", + "try:\n", + "\n", + " store = FeatureStore(repo_path=\"client\")\n", + "\n", + " print(\"\\n--- Historical features for training ---\")\n", + " fetch_historical_features_entity_df(store, for_batch_scoring=False)\n", + "\n", + " print(\"\\n--- Historical features for batch scoring ---\")\n", + " fetch_historical_features_entity_df(store, for_batch_scoring=True)\n", + "\n", + " try:\n", + " print(\"\\n--- Load features into online store/materialize_incremental ---\")\n", + " feature_views= store.list_feature_views()\n", + " if not feature_views:\n", + " raise PermissionError(\"No access to feature-views or no feature-views available.\")\n", + " store.materialize_incremental(end_date=datetime.now())\n", + " except PermissionError as pe:\n", + " print(f\"Permission error: {pe}\")\n", + " except Exception as e:\n", + " print(f\"An occurred while performing materialize incremental: {e}\")\n", + "\n", + " print(\"\\n--- Online features ---\")\n", + " fetch_online_features(store)\n", + "\n", + " print(\"\\n--- Online features retrieved (instead) through a feature service---\")\n", + " fetch_online_features(store, source=\"feature_service\")\n", + "\n", + " print(\n", + " \"\\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---\"\n", + " )\n", + " fetch_online_features(store, source=\"push\")\n", + "\n", + " print(\"\\n--- Simulate a stream event ingestion of the hourly stats df ---\")\n", + " event_df = pd.DataFrame.from_dict(\n", + " {\n", + " \"driver_id\": [1001],\n", + " \"event_timestamp\": [datetime.now()],\n", + " \"created\": [datetime.now()],\n", + " \"conv_rate\": [1.0],\n", + " \"acc_rate\": [1.0],\n", + " \"avg_daily_trips\": [1000],\n", + " }\n", + " )\n", + " store.push(\"driver_stats_push_source\", event_df, to=PushMode.ONLINE_AND_OFFLINE)\n", + "\n", + " print(\"\\n--- Online features again with updated values from a stream push---\")\n", + " fetch_online_features(store, source=\"push\")\n", + "\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n" + ], + "id": "14b7ad38368db767", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running Feast RBAC test for Read only User...\r\n", + "\n", + "--- Historical features for training ---\n", + "Handling connection for 8083\n", + "Handling connection for 8081\n", + " driver_id event_timestamp label_driver_reported_satisfaction \\\n", + "0 1001 2021-04-12 10:59:42+00:00 1 \n", + "1 1002 2021-04-12 08:12:10+00:00 5 \n", + "2 1003 2021-04-12 16:40:26+00:00 3 \n", + "\n", + " val_to_add val_to_add_2 conv_rate acc_rate avg_daily_trips \\\n", + "0 1 10 0.568053 0.225828 614 \n", + "1 2 20 0.449785 0.860379 686 \n", + "2 3 30 0.153563 0.300467 725 \n", + "\n", + " conv_rate_plus_val1 conv_rate_plus_val2 \n", + "0 1.568053 10.568053 \n", + "1 2.449785 20.449785 \n", + "2 3.153563 30.153563 \n", + "\n", + "--- Historical features for batch scoring ---\n", + "Handling connection for 8081\n", + " driver_id event_timestamp \\\n", + "0 1002 2025-03-05 19:08:29.434969+00:00 \n", + "1 1001 2025-03-05 19:08:29.434969+00:00 \n", + "2 1003 2025-03-05 19:08:29.434969+00:00 \n", + "\n", + " label_driver_reported_satisfaction val_to_add val_to_add_2 conv_rate \\\n", + "0 5 2 20 0.806965 \n", + "1 1 1 10 0.432434 \n", + "2 3 3 30 0.295015 \n", + "\n", + " acc_rate avg_daily_trips conv_rate_plus_val1 conv_rate_plus_val2 \n", + "0 0.290849 144 2.806965 20.806965 \n", + "1 0.076731 357 1.432434 10.432434 \n", + "2 0.962840 733 3.295015 30.295015 \n", + "\n", + "--- Load features into online store/materialize_incremental ---\n", + "Materializing \u001B[1m\u001B[32m2\u001B[0m feature views to \u001B[1m\u001B[32m2025-03-05 14:08:30-05:00\u001B[0m into the \u001B[1m\u001B[32mremote\u001B[0m online store.\n", + "\n", + "\u001B[1m\u001B[32mdriver_hourly_stats_fresh\u001B[0m from \u001B[1m\u001B[32m2025-03-04 14:08:30-05:00\u001B[0m to \u001B[1m\u001B[32m2025-03-05 14:08:30-05:00\u001B[0m:\n", + "Handling connection for 8081\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/5 [00:00 03-uninstall.ipynb", + "id": "38c54e92643e0bda" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/operator-rbac/README.md b/examples/operator-rbac/README.md new file mode 100644 index 00000000000..9b0f6e9d23f --- /dev/null +++ b/examples/operator-rbac/README.md @@ -0,0 +1,6 @@ +# Running the Feast RBAC example on Kubernetes using the Feast Operator. + +1. [1-setup-operator-rbac.ipynb](1-setup-operator-rbac.ipynb) will guide you through how to setup Role-Based Access Control (RBAC) for Feast using [Feast Operator](../../infra/feast-operator/) with the Kubernetes Authentication type. This Feast Admin Step required to setup operator and Feast RBAC on K8s environment. +2. [2-client.ipynb](2-client.ipynb) Validate with RBAC with client example using different test cases with service account token locally. +3. [03-uninstall.ipynb](03-uninstall.ipynb) Clear the installed deployments and K8s Objects. + diff --git a/examples/operator-rbac/client/feature_store.yaml b/examples/operator-rbac/client/feature_store.yaml new file mode 100644 index 00000000000..49a4c426363 --- /dev/null +++ b/examples/operator-rbac/client/feature_store.yaml @@ -0,0 +1,15 @@ +project: feast_rbac +provider: local +offline_store: + host: localhost + type: remote + port: 8081 +online_store: + path: http://localhost:8082 + type: remote +registry: + path: localhost:8083 + registry_type: remote +auth: + type: kubernetes +entity_key_serialization_version: 3 diff --git a/examples/operator-rbac/permissions_apply.py b/examples/operator-rbac/permissions_apply.py new file mode 100644 index 00000000000..9f5e6c9a370 --- /dev/null +++ b/examples/operator-rbac/permissions_apply.py @@ -0,0 +1,21 @@ +from feast.feast_object import ALL_RESOURCE_TYPES +from feast.permissions.action import READ, AuthzedAction, ALL_ACTIONS +from feast.permissions.permission import Permission +from feast.permissions.policy import RoleBasedPolicy + +admin_roles = ["feast-writer"] +user_roles = ["feast-reader"] + +user_perm = Permission( + name="feast_user_permission", + types=ALL_RESOURCE_TYPES, + policy=RoleBasedPolicy(roles=user_roles), + actions=[AuthzedAction.DESCRIBE] + READ +) + +admin_perm = Permission( + name="feast_admin_permission", + types=ALL_RESOURCE_TYPES, + policy=RoleBasedPolicy(roles=admin_roles), + actions=ALL_ACTIONS +) diff --git a/infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml b/infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml index 6751be60e9c..33225b2edfb 100644 --- a/infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml +++ b/infra/feast-operator/config/samples/v1alpha1_featurestore_kubernetes_auth.yaml @@ -3,9 +3,18 @@ kind: FeatureStore metadata: name: sample-kubernetes-auth spec: - feastProject: my_project + feastProject: feast_rbac authz: kubernetes: roles: - - reader - - writer + - feast-writer + - feast-reader + services: + offlineStore: + server: {} + onlineStore: + server: {} + registry: + local: + server: {} + ui: {} From 7483fae21ecda42411298bc80503a4c500f7e732 Mon Sep 17 00:00:00 2001 From: Abdul Hameed Date: Thu, 6 Mar 2025 08:33:04 -0500 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Francisco Arceo Signed-off-by: Abdul Hameed --- examples/operator-rbac/1-setup-operator-rbac.ipynb | 4 ++-- examples/operator-rbac/2-client.ipynb | 6 +++--- examples/operator-rbac/README.md | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/operator-rbac/1-setup-operator-rbac.ipynb b/examples/operator-rbac/1-setup-operator-rbac.ipynb index 04e6a7ca510..ea1f112ce1b 100644 --- a/examples/operator-rbac/1-setup-operator-rbac.ipynb +++ b/examples/operator-rbac/1-setup-operator-rbac.ipynb @@ -7,7 +7,7 @@ "# Feast Operator with RBAC Configuration\n", "## Objective\n", "\n", - "This demo provides a reference implementation of a runbook on how to enable Role-Based Access Control (RBAC) for Feast using the Feast Operator with the Kubernetes authentication type.\n", + "This demo provides a reference implementation of a runbook on how to enable Role-Based Access Control (RBAC) for Feast using the Feast Operator with the Kubernetes authentication type. This serves as useful reference material for a cluster admin / MLOps engineer.\n", "\n", "The demo steps include deploying the Feast Operator, creating Feast instances with server components (registry, offline store, online store), and Feast client testing locally. The goal is to ensure secure access control for Feast instances deployed by the Feast Operator.\n", " \n", @@ -113,7 +113,7 @@ "cell_type": "markdown", "source": [ "## Feast Admin Steps:\n", - "Feast Admin or MLOps Engineers may require Kubernetes Cluster Admin roles when working with OpenShift or Kubernetes clusters. Below is the list of steps Required to perform setting up Feast RBAC with Operator by Admin or MLOps Engineers.\n", + "Feast Admins or MLOps Engineers may require Kubernetes Cluster Admin roles when working with OpenShift or Kubernetes clusters. Below is the list of steps Required to set up Feast RBAC with the Operator by an Admin or MLOps Engineer.\n", "\n", "1. **Install the Feast Operator**\n", "2. **Install the Feast services via FeatureStore CR**\n", diff --git a/examples/operator-rbac/2-client.ipynb b/examples/operator-rbac/2-client.ipynb index b99157f199b..f5e29f1dfcc 100644 --- a/examples/operator-rbac/2-client.ipynb +++ b/examples/operator-rbac/2-client.ipynb @@ -6,7 +6,7 @@ "source": [ "## Feast Client with RBAC\n", "### RBAC Kubernetes Authentication\n", - "This Feast **Role-Based Access Control (RBAC)** in Kubernetes support authentication **inside a Kubernetes pod** and **outside a pod** when running a local script.\n", + "Feast **Role-Based Access Control (RBAC)** in Kubernetes supports authentication **inside a Kubernetes pod** and **outside a pod** when running a local script.\n", "### Inside a Kubernetes Pod\n", "Feast automatically retrieves the Kubernetes ServiceAccount token from:\n", "```\n", @@ -351,7 +351,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "**Step 2:Test function features from offline, online and materialize_incremental etc**", + "source": "**Step 2: Test misc functions from offline, online, materialize_incremental, and others**", "id": "140c909fa8bcc6ab" }, { @@ -363,7 +363,7 @@ }, "cell_type": "code", "source": [ - "!echo \"Running Feast RBAC test for Read only User...\"\n", + "!echo \"Running the Feast RBAC test for a Read only User...\"\n", "from feast.data_source import PushMode\n", "from feast import FeatureStore\n", "from datetime import datetime\n", diff --git a/examples/operator-rbac/README.md b/examples/operator-rbac/README.md index 9b0f6e9d23f..9c0a0461678 100644 --- a/examples/operator-rbac/README.md +++ b/examples/operator-rbac/README.md @@ -1,6 +1,6 @@ # Running the Feast RBAC example on Kubernetes using the Feast Operator. -1. [1-setup-operator-rbac.ipynb](1-setup-operator-rbac.ipynb) will guide you through how to setup Role-Based Access Control (RBAC) for Feast using [Feast Operator](../../infra/feast-operator/) with the Kubernetes Authentication type. This Feast Admin Step required to setup operator and Feast RBAC on K8s environment. -2. [2-client.ipynb](2-client.ipynb) Validate with RBAC with client example using different test cases with service account token locally. +1. [1-setup-operator-rbac.ipynb](1-setup-operator-rbac.ipynb) will guide you through how to setup Role-Based Access Control (RBAC) for Feast using the [Feast Operator](../../infra/feast-operator/) and Kubernetes Authentication. This Feast Admin Step requires you to setup the operator and Feast RBAC on K8s. +2. [2-client.ipynb](2-client.ipynb) Validate the RBAC with the client example using different test cases using a service account token locally. 3. [03-uninstall.ipynb](03-uninstall.ipynb) Clear the installed deployments and K8s Objects. From 597441dad5207786cf79e6d466ff15ae55c130d5 Mon Sep 17 00:00:00 2001 From: Abdul Hameed Date: Thu, 6 Mar 2025 09:55:36 -0500 Subject: [PATCH 3/3] addressed the review comments Signed-off-by: Abdul Hameed --- .../operator-rbac/1-setup-operator-rbac.ipynb | 143 ++-- examples/operator-rbac/2-client.ipynb | 650 ++++++++---------- examples/operator-rbac/permissions_apply.py | 14 +- 3 files changed, 365 insertions(+), 442 deletions(-) diff --git a/examples/operator-rbac/1-setup-operator-rbac.ipynb b/examples/operator-rbac/1-setup-operator-rbac.ipynb index ea1f112ce1b..69cc285a01c 100644 --- a/examples/operator-rbac/1-setup-operator-rbac.ipynb +++ b/examples/operator-rbac/1-setup-operator-rbac.ipynb @@ -27,8 +27,9 @@ "* `Registry Server`: Handles metadata storage for feature definitions.\n", "* `Online Store Server`: Uses the `Registry Server` to query metadata and is responsible for low-latency serving of features.\n", "* `Offline Store Server`: Uses the `Registry Server` to query metadata and provides access to batch data for historical feature retrieval.\n", - "* `Kubernetes` Authentication types for RBAC Configuration for Feast resources.\n", - "* Setting update Feast RBAC based on roles assigned then validating with client example.\n" + "\n", + "Additionally, we will cover:\n", + "* RBAC Configuration with Kubernetes Authentication for Feast resources." ] }, { @@ -61,8 +62,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:47:55.278200Z", - "start_time": "2025-03-05T18:47:54.932229Z" + "end_time": "2025-03-06T18:27:31.474254Z", + "start_time": "2025-03-06T18:27:31.012088Z" } }, "cell_type": "code", @@ -80,7 +81,7 @@ ] } ], - "execution_count": 153 + "execution_count": 1 }, { "metadata": {}, @@ -90,8 +91,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:48:07.240717Z", - "start_time": "2025-03-05T18:48:04.041808Z" + "end_time": "2025-03-06T18:32:23.198122Z", + "start_time": "2025-03-06T18:32:22.930547Z" } }, "cell_type": "code", @@ -102,11 +103,11 @@ "output_type": "stream", "text": [ "NAME STATUS AGE\r\n", - "feast Active 12s\r\n" + "feast Active 4m52s\r\n" ] } ], - "execution_count": 154 + "execution_count": 2 }, { "metadata": {}, @@ -127,13 +128,13 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:48:25.617201Z", - "start_time": "2025-03-05T18:48:12.812694Z" + "end_time": "2025-03-06T18:32:40.721042Z", + "start_time": "2025-03-06T18:32:28.484245Z" } }, "cell_type": "code", "source": [ - "## Use this install command from a release branch (e.g. 'v0.43-branch')\n", + "## Use this install command from a stable branch \n", "!kubectl apply -f ../../infra/feast-operator/dist/install.yaml\n", "\n", "## OR, for the latest code/builds, use one the following commands from the 'master' branch\n", @@ -165,7 +166,7 @@ ] } ], - "execution_count": 155 + "execution_count": 3 }, { "cell_type": "markdown", @@ -179,8 +180,8 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:48:41.993231Z", - "start_time": "2025-03-05T18:48:38.573278Z" + "end_time": "2025-03-06T18:34:39.847211Z", + "start_time": "2025-03-06T18:34:39.378680Z" } }, "source": [ @@ -216,7 +217,7 @@ ] } ], - "execution_count": 156 + "execution_count": 4 }, { "cell_type": "markdown", @@ -230,8 +231,8 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:49:07.735147Z", - "start_time": "2025-03-05T18:48:47.817817Z" + "end_time": "2025-03-06T18:35:05.202176Z", + "start_time": "2025-03-06T18:35:02.498106Z" } }, "source": [ @@ -243,25 +244,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "NAME READY STATUS RESTARTS AGE\r\n", - "pod/feast-sample-kubernetes-auth-774f6df8df-9ngcl 0/4 Init:0/1 0 5s\r\n", + "NAME READY STATUS RESTARTS AGE\r\n", + "pod/feast-sample-kubernetes-auth-774f6df8df-95nc6 0/4 Running 0 22s\r\n", "\r\n", "NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\r\n", - "service/feast-sample-kubernetes-auth-offline ClusterIP 10.96.2.92 80/TCP 5s\r\n", - "service/feast-sample-kubernetes-auth-online ClusterIP 10.96.128.237 80/TCP 5s\r\n", - "service/feast-sample-kubernetes-auth-registry ClusterIP 10.96.209.35 80/TCP 5s\r\n", - "service/feast-sample-kubernetes-auth-ui ClusterIP 10.96.76.211 80/TCP 5s\r\n", + "service/feast-sample-kubernetes-auth-offline ClusterIP 10.96.38.230 80/TCP 22s\r\n", + "service/feast-sample-kubernetes-auth-online ClusterIP 10.96.140.194 80/TCP 22s\r\n", + "service/feast-sample-kubernetes-auth-registry ClusterIP 10.96.140.31 80/TCP 22s\r\n", + "service/feast-sample-kubernetes-auth-ui ClusterIP 10.96.26.21 80/TCP 22s\r\n", "\r\n", "NAME READY UP-TO-DATE AVAILABLE AGE\r\n", - "deployment.apps/feast-sample-kubernetes-auth 0/1 1 0 5s\r\n", + "deployment.apps/feast-sample-kubernetes-auth 0/1 1 0 22s\r\n", "\r\n", "NAME DESIRED CURRENT READY AGE\r\n", - "replicaset.apps/feast-sample-kubernetes-auth-774f6df8df 1 1 0 5s\r\n", + "replicaset.apps/feast-sample-kubernetes-auth-774f6df8df 1 1 0 22s\r\n", "deployment.apps/feast-sample-kubernetes-auth condition met\r\n" ] } ], - "execution_count": 157 + "execution_count": 5 }, { "cell_type": "markdown", @@ -274,8 +275,8 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:49:15.695148Z", - "start_time": "2025-03-05T18:49:15.499064Z" + "end_time": "2025-03-06T18:35:55.728523Z", + "start_time": "2025-03-06T18:35:55.452894Z" } }, "source": [ @@ -287,25 +288,25 @@ "output_type": "stream", "text": [ "NAME STATUS AGE\r\n", - "sample-kubernetes-auth Ready 34s\r\n" + "sample-kubernetes-auth Ready 76s\r\n" ] } ], - "execution_count": 158 + "execution_count": 6 }, { "metadata": {}, "cell_type": "markdown", "source": [ "## Configure the RBAC Permissions\n", - "we have defined permission in `permissions_apply.py`." + "As we have created Kubernetes roles in FeatureStore CR to manage access control for Feast objects, the Python script `permissions_apply.py` will apply these roles to configure permissions. See the detailed code example below with comments." ] }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:49:43.015317Z", - "start_time": "2025-03-05T18:49:42.826986Z" + "end_time": "2025-03-06T18:37:17.062072Z", + "start_time": "2025-03-06T18:37:16.930026Z" } }, "cell_type": "code", @@ -318,37 +319,43 @@ "name": "stdout", "output_type": "stream", "text": [ + "# Necessary modules for permissions and policies in Feast for RBAC\r\n", "from feast.feast_object import ALL_RESOURCE_TYPES\r\n", "from feast.permissions.action import READ, AuthzedAction, ALL_ACTIONS\r\n", "from feast.permissions.permission import Permission\r\n", "from feast.permissions.policy import RoleBasedPolicy\r\n", "\r\n", - "admin_roles = [\"feast-writer\"]\r\n", - "user_roles = [\"feast-reader\"]\r\n", + "# Define K8s roles same as created with FeatureStore CR\r\n", + "admin_roles = [\"feast-writer\"] # Full access (can create, update, delete ) Feast Resources\r\n", + "user_roles = [\"feast-reader\"] # Read-only access on Feast Resources\r\n", "\r\n", + "# User permissions (feast_user_permission)\r\n", + "# - Grants read and describing Feast objects access\r\n", "user_perm = Permission(\r\n", " name=\"feast_user_permission\",\r\n", " types=ALL_RESOURCE_TYPES,\r\n", " policy=RoleBasedPolicy(roles=user_roles),\r\n", - " actions=[AuthzedAction.DESCRIBE] + READ\r\n", + " actions=[AuthzedAction.DESCRIBE] + READ # Read access (READ_ONLINE, READ_OFFLINE) + describe other Feast Resources.\r\n", ")\r\n", "\r\n", + "# Admin permissions (feast_admin_permission)\r\n", + "# - Grants full control over all resources\r\n", "admin_perm = Permission(\r\n", " name=\"feast_admin_permission\",\r\n", " types=ALL_RESOURCE_TYPES,\r\n", " policy=RoleBasedPolicy(roles=admin_roles),\r\n", - " actions=ALL_ACTIONS\r\n", + " actions=ALL_ACTIONS # Full permissions: CREATE, UPDATE, DELETE, READ, WRITE\r\n", ")\r\n" ] } ], - "execution_count": 159 + "execution_count": 7 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:49:48.693053Z", - "start_time": "2025-03-05T18:49:48.261653Z" + "end_time": "2025-03-06T18:37:31.662484Z", + "start_time": "2025-03-06T18:37:31.139869Z" } }, "cell_type": "code", @@ -357,14 +364,14 @@ "!kubectl cp permissions_apply.py $(kubectl get pods -l 'feast.dev/name=sample-kubernetes-auth' -ojsonpath=\"{.items[*].metadata.name}\"):/feast-data/feast_rbac/feature_repo -c online" ], "outputs": [], - "execution_count": 160 + "execution_count": 8 }, { "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:49:58.767591Z", - "start_time": "2025-03-05T18:49:55.886459Z" + "end_time": "2025-03-06T18:37:38.003082Z", + "start_time": "2025-03-06T18:37:37.662378Z" } }, "source": [ @@ -392,7 +399,7 @@ ] } ], - "execution_count": 161 + "execution_count": 9 }, { "metadata": {}, @@ -402,8 +409,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:50:15.795804Z", - "start_time": "2025-03-05T18:50:05.431746Z" + "end_time": "2025-03-06T18:37:56.131390Z", + "start_time": "2025-03-06T18:37:45.483916Z" } }, "cell_type": "code", @@ -428,13 +435,13 @@ " warnings.warn(\r\n", "Created project \u001B[1m\u001B[32mfeast_rbac\u001B[0m\r\n", "Created entity \u001B[1m\u001B[32mdriver\u001B[0m\r\n", - "Created feature view \u001B[1m\u001B[32mdriver_hourly_stats_fresh\u001B[0m\r\n", "Created feature view \u001B[1m\u001B[32mdriver_hourly_stats\u001B[0m\r\n", - "Created on demand feature view \u001B[1m\u001B[32mtransformed_conv_rate_fresh\u001B[0m\r\n", + "Created feature view \u001B[1m\u001B[32mdriver_hourly_stats_fresh\u001B[0m\r\n", "Created on demand feature view \u001B[1m\u001B[32mtransformed_conv_rate\u001B[0m\r\n", + "Created on demand feature view \u001B[1m\u001B[32mtransformed_conv_rate_fresh\u001B[0m\r\n", + "Created feature service \u001B[1m\u001B[32mdriver_activity_v2\u001B[0m\r\n", "Created feature service \u001B[1m\u001B[32mdriver_activity_v1\u001B[0m\r\n", "Created feature service \u001B[1m\u001B[32mdriver_activity_v3\u001B[0m\r\n", - "Created feature service \u001B[1m\u001B[32mdriver_activity_v2\u001B[0m\r\n", "Created permission \u001B[1m\u001B[32mfeast_admin_permission\u001B[0m\r\n", "Created permission \u001B[1m\u001B[32mfeast_user_permission\u001B[0m\r\n", "\r\n", @@ -444,7 +451,7 @@ ] } ], - "execution_count": 162 + "execution_count": 10 }, { "cell_type": "markdown", @@ -455,8 +462,8 @@ "cell_type": "code", "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:51:01.464386Z", - "start_time": "2025-03-05T18:50:19.362018Z" + "end_time": "2025-03-06T18:38:45.881715Z", + "start_time": "2025-03-06T18:38:04.170364Z" } }, "source": [ @@ -552,8 +559,8 @@ " roles:\r\n", " - feast-writer\r\n", "meta:\r\n", - " createdTimestamp: '2025-03-05T18:50:15.487200Z'\r\n", - " lastUpdatedTimestamp: '2025-03-05T18:50:15.487200Z'\r\n", + " createdTimestamp: '2025-03-06T18:37:55.742625Z'\r\n", + " lastUpdatedTimestamp: '2025-03-06T18:37:55.742625Z'\r\n", "\r\n", ": MADV_DONTNEED does not work (memset will be used instead)\r\n", ": (This is the expected behaviour if you are running under QEMU)\r\n", @@ -586,13 +593,13 @@ " roles:\r\n", " - feast-reader\r\n", "meta:\r\n", - " createdTimestamp: '2025-03-05T18:50:15.487974Z'\r\n", - " lastUpdatedTimestamp: '2025-03-05T18:50:15.487974Z'\r\n", + " createdTimestamp: '2025-03-06T18:37:55.743643Z'\r\n", + " lastUpdatedTimestamp: '2025-03-06T18:37:55.743643Z'\r\n", "\r\n" ] } ], - "execution_count": 163 + "execution_count": 11 }, { "metadata": {}, @@ -629,8 +636,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:51:17.394789Z", - "start_time": "2025-03-05T18:51:16.725533Z" + "end_time": "2025-03-06T18:42:04.122440Z", + "start_time": "2025-03-06T18:42:03.397214Z" } }, "cell_type": "code", @@ -655,7 +662,7 @@ ] } ], - "execution_count": 164 + "execution_count": 12 }, { "metadata": {}, @@ -665,8 +672,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:51:23.958145Z", - "start_time": "2025-03-05T18:51:23.614975Z" + "end_time": "2025-03-06T18:42:07.992216Z", + "start_time": "2025-03-06T18:42:07.721628Z" } }, "cell_type": "code", @@ -685,7 +692,7 @@ ] } ], - "execution_count": 165 + "execution_count": 13 }, { "metadata": {}, @@ -695,8 +702,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:51:30.716036Z", - "start_time": "2025-03-05T18:51:30.057571Z" + "end_time": "2025-03-06T18:42:11.651408Z", + "start_time": "2025-03-06T18:42:11.097231Z" } }, "cell_type": "code", @@ -721,12 +728,12 @@ ] } ], - "execution_count": 166 + "execution_count": 14 }, { "metadata": {}, "cell_type": "markdown", - "source": "## Next Run Client notebook -> 2-client.ipynb" + "source": "[Next Run Client notebook](./2-client.ipynb)" } ], "metadata": { diff --git a/examples/operator-rbac/2-client.ipynb b/examples/operator-rbac/2-client.ipynb index f5e29f1dfcc..cf9d57cb5bc 100644 --- a/examples/operator-rbac/2-client.ipynb +++ b/examples/operator-rbac/2-client.ipynb @@ -5,8 +5,13 @@ "cell_type": "markdown", "source": [ "## Feast Client with RBAC\n", - "### RBAC Kubernetes Authentication\n", - "Feast **Role-Based Access Control (RBAC)** in Kubernetes supports authentication **inside a Kubernetes pod** and **outside a pod** when running a local script.\n", + "### Kubernetes RBAC Authorization\n", + "\n", + "## Feast Role-Based Access Control (RBAC) in Kubernetes \n", + "\n", + "Feast **Role-Based Access Control (RBAC)** in Kubernetes supports authentication both **inside a Kubernetes pod** and for **external clients** using the `LOCAL_K8S_TOKEN` environment variable. \n", + "\n", + "\n", "### Inside a Kubernetes Pod\n", "Feast automatically retrieves the Kubernetes ServiceAccount token from:\n", "```\n", @@ -18,17 +23,16 @@ "- Developer just need create the binding with role and service account accordingly.\n", "- Code Reference: \n", "[Feast Kubernetes Auth Client Manager (Pod Token Usage)](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/permissions/client/kubernetes_auth_client_manager.py#L15) \n", - "- See the example will use service account from pod [Example](https://github.com/feast-dev/feast/blob/master/examples/rbac-remote/client/k8s/)\n", + "- Using a service account from a pod [Example](https://github.com/feast-dev/feast/blob/master/examples/rbac-remote/client/k8s/)\n", "\n", - "### Outside a Kubernetes Pod (Local Machine)\n", - "If running Feast outside of Kubernetes, authentication requires setting the token manually:\n", + "### Outside a Kubernetes Pod (External Clients & Local Testing)\n", + " \n", + "If running Feast outside of Kubernetes, authentication requires setting the token manually to the environment variable `LOCAL_K8S_TOKEN` :\n", "```sh\n", "export LOCAL_K8S_TOKEN=\"your-service-account-token\"\n", "```\n", - "Feast will use this token for authentication.\n", "\n", - "Reference: \n", - "[Feast Authentication via `LOCAL_K8S_TOKEN`](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/permissions/client/kubernetes_auth_client_manager.py#L50)" + "For more details, refer the user guide: [Kubernetes RBAC Authorization](https://docs.feast.dev/master/getting-started/components/authz_manager#kubernetes-rbac-authorization) \n" ], "id": "bb0145c9c1f6ebcc" }, @@ -54,8 +58,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:51:44.898752Z", - "start_time": "2025-03-05T18:51:44.757430Z" + "end_time": "2025-03-06T18:47:45.151296Z", + "start_time": "2025-03-06T18:47:45.024854Z" } }, "cell_type": "code", @@ -84,19 +88,19 @@ ] } ], - "execution_count": 7 + "execution_count": 1 }, { "metadata": {}, "cell_type": "markdown", - "source": "**The Operator creates the client ConfigMap containing the feature_store.yaml. We can retrieve it and port froward to local**", + "source": "**The Operator client feature store ConfigMap** containing the `feature_store.yaml `settings. We can retrieve it and port froward to local as we are testing locally.", "id": "84f73e09711bff9f" }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:51:50.571415Z", - "start_time": "2025-03-05T18:51:50.339196Z" + "end_time": "2025-03-06T18:46:36.029308Z", + "start_time": "2025-03-06T18:46:35.712532Z" } }, "cell_type": "code", @@ -125,7 +129,7 @@ ] } ], - "execution_count": 8 + "execution_count": 34 }, { "metadata": {}, @@ -142,8 +146,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T19:07:49.786503Z", - "start_time": "2025-03-05T19:07:49.754932Z" + "end_time": "2025-03-06T18:47:55.237205Z", + "start_time": "2025-03-06T18:47:55.226143Z" } }, "cell_type": "code", @@ -177,7 +181,7 @@ ] } ], - "execution_count": 19 + "execution_count": 2 }, { "metadata": {}, @@ -188,8 +192,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:52:03.520366Z", - "start_time": "2025-03-05T18:52:03.512357Z" + "end_time": "2025-03-06T18:48:00.150752Z", + "start_time": "2025-03-06T18:48:00.143370Z" } }, "cell_type": "code", @@ -218,29 +222,36 @@ ], "id": "70bdbcd7b3fe44", "outputs": [], - "execution_count": 10 + "execution_count": 3 }, { "metadata": {}, "cell_type": "markdown", "source": [ - "**Generating training data. The following test functions were copied from the `test_workflow.py` template but we added `try` blocks to print only \n", - "the relevant error messages, since we expect to receive errors from the permission enforcement modules.**" + "**Generating training data**. The following test functions were copied from the `test_workflow.py` template but we added `try` blocks to print only \n", + "the relevant error messages, since we expect to receive errors from the permission enforcement modules." ], "id": "8c9e27ec4ed8ca2c" }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:52:09.002273Z", - "start_time": "2025-03-05T18:52:08.996378Z" + "end_time": "2025-03-06T20:16:04.254201Z", + "start_time": "2025-03-06T20:16:04.245605Z" } }, "cell_type": "code", "source": [ "from feast import FeatureStore\n", + "from feast.data_source import PushMode\n", + "from datetime import datetime\n", + "import pandas as pd\n", + "\n", + "# Initialize Feature Store\n", + "store = FeatureStore(repo_path=\"client\")\n", "\n", "def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):\n", + " \"\"\"Fetch historical features for training or batch scoring.\"\"\"\n", " try:\n", " entity_df = pd.DataFrame.from_dict(\n", " {\n", @@ -251,7 +262,6 @@ " datetime(2021, 4, 12, 16, 40, 26),\n", " ],\n", " \"label_driver_reported_satisfaction\": [1, 5, 3],\n", - " # values we're using for an on-demand transformation\n", " \"val_to_add\": [1, 2, 3],\n", " \"val_to_add_2\": [10, 20, 30],\n", " }\n", @@ -268,18 +278,18 @@ " \"transformed_conv_rate:conv_rate_plus_val1\",\n", " \"transformed_conv_rate:conv_rate_plus_val2\",\n", " ],\n", - "\n", " ).to_df()\n", - " print(training_df.head())\n", + " print(f\"Successfully fetched {'batch scoring' if for_batch_scoring else 'training'} historical features:\\n\", training_df.head())\n", "\n", + " except PermissionError:\n", + " print(\"\\n*** PERMISSION DENIED *** Cannot fetch historical features.\")\n", " except Exception as e:\n", - " print(f\"An error occurred while fetching historical features: {e}\")\n", - "\n", + " print(f\"Unexpected error while fetching historical features: {e}\")\n", "\n", - "def fetch_online_features(store, source: str = \"\"):\n", + "def fetch_online_features(store: FeatureStore, source: str = \"\"):\n", + " \"\"\"Fetch online features from the feature store.\"\"\"\n", " try:\n", " entity_rows = [\n", - " # {join_key: entity_value}\n", " {\n", " \"driver_id\": 1001,\n", " \"val_to_add\": 1000,\n", @@ -301,19 +311,95 @@ " \"transformed_conv_rate:conv_rate_plus_val1\",\n", " \"transformed_conv_rate:conv_rate_plus_val2\",\n", " ]\n", + "\n", " returned_features = store.get_online_features(\n", " features=features_to_fetch,\n", " entity_rows=entity_rows,\n", " ).to_dict()\n", + "\n", + " print(f\"Successfully fetched online features {'via feature service' if source else 'directly'}:\\n\")\n", " for key, value in sorted(returned_features.items()):\n", - " print(key, \" : \", value)\n", + " print(f\"{key} : {value}\")\n", + "\n", + " except PermissionError:\n", + " print(\"\\n*** PERMISSION DENIED *** Cannot fetch online features.\")\n", + " except Exception as e:\n", + " print(f\"Unexpected error while fetching online features: {e}\")\n", + "\n", + "def check_permissions():\n", + " \"\"\"Check user role, test various Feast operations,.\"\"\"\n", + "\n", + " feature_views = []\n", + "\n", + " # Step 1: List feature views\n", + " print(\"\\n--- List feature views ---\")\n", + " try:\n", + " feature_views = store.list_feature_views()\n", + " if not feature_views:\n", + " print(\"No feature views found. You might not have access or they haven't been created.\")\n", + " has_feature_view_access = False\n", + " else:\n", + " print(f\"Successfully listed {len(feature_views)} feature views:\")\n", + " for fv in feature_views:\n", + " print(f\" - {fv.name}\")\n", "\n", + " except PermissionError:\n", + " print(\"\\n*** PERMISSION DENIED *** Cannot list feature views.\")\n", + " has_feature_view_access = False\n", " except Exception as e:\n", - " print(f\"An error occurred while fetching online features: {e}\")" + " print(f\"Unexpected error listing feature views: {e}\")\n", + " has_feature_view_access = False\n", + "\n", + " # Step 2: Fetch Historical Features\n", + " print(\"\\n--- Fetching Historical Features for Training ---\")\n", + " fetch_historical_features_entity_df(store, for_batch_scoring=False)\n", + "\n", + " print(\"\\n--- Fetching Historical Features for Batch Scoring ---\")\n", + " fetch_historical_features_entity_df(store, for_batch_scoring=True)\n", + "\n", + " # Step 3: Apply Feature Store\n", + " print(\"\\n--- Write to Feature Store ---\")\n", + " try:\n", + " store.apply(feature_views)\n", + " print(\"User has write access to the feature store.\")\n", + " except PermissionError:\n", + " print(\"\\n*** PERMISSION DENIED *** User lacks permission to modify the feature store.\")\n", + " except Exception as e:\n", + " print(f\"Unexpected error testing write access: {e}\")\n", + "\n", + " # Step 4: Fetch Online Features\n", + " print(\"\\n--- Fetching Online Features ---\")\n", + " fetch_online_features(store)\n", + "\n", + " print(\"\\n--- Fetching Online Features via Feature Service ---\")\n", + " fetch_online_features(store, source=\"feature_service\")\n", + "\n", + " print(\"\\n--- Fetching Online Features via Push Source ---\")\n", + " fetch_online_features(store, source=\"push\")\n", + "\n", + " print(\"\\n--- Performing Push Source ---\")\n", + " # Step 5: Simulate Event Push (Streaming Ingestion)\n", + " try:\n", + " event_df = pd.DataFrame.from_dict(\n", + " {\n", + " \"driver_id\": [1001],\n", + " \"event_timestamp\": [datetime.now()],\n", + " \"created\": [datetime.now()],\n", + " \"conv_rate\": [1.0],\n", + " \"acc_rate\": [1.0],\n", + " \"avg_daily_trips\": [1000],\n", + " }\n", + " )\n", + " store.push(\"driver_stats_push_source\", event_df, to=PushMode.ONLINE_AND_OFFLINE)\n", + " print(\"Successfully pushed a test event.\")\n", + " except PermissionError:\n", + " print(\"\\n*** PERMISSION DENIED *** Cannot push event (no write access).\")\n", + " except Exception as e:\n", + " print(f\"Unexpected error while pushing event: {e}\")\n" ], "id": "934963c5f6b18930", "outputs": [], - "execution_count": 11 + "execution_count": 51 }, { "metadata": {}, @@ -327,8 +413,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T18:52:20.736183Z", - "start_time": "2025-03-05T18:52:20.654808Z" + "end_time": "2025-03-06T20:12:44.771268Z", + "start_time": "2025-03-06T20:12:44.691353Z" } }, "cell_type": "code", @@ -341,12 +427,12 @@ "'Token Retrieved: ***** (hidden for security)'" ] }, - "execution_count": 12, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 12 + "execution_count": 48 }, { "metadata": {}, @@ -357,68 +443,14 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-05T19:08:31.257351Z", - "start_time": "2025-03-05T19:08:25.274715Z" + "end_time": "2025-03-06T20:16:16.680582Z", + "start_time": "2025-03-06T20:16:14.930480Z" } }, "cell_type": "code", "source": [ - "!echo \"Running the Feast RBAC test for a Read only User...\"\n", - "from feast.data_source import PushMode\n", - "from feast import FeatureStore\n", - "from datetime import datetime\n", - "import pandas as pd\n", - "\n", - "try:\n", - "\n", - " store = FeatureStore(repo_path=\"client\")\n", - "\n", - " print(\"\\n--- Historical features for training ---\")\n", - " fetch_historical_features_entity_df(store, for_batch_scoring=False)\n", - "\n", - " print(\"\\n--- Historical features for batch scoring ---\")\n", - " fetch_historical_features_entity_df(store, for_batch_scoring=True)\n", - "\n", - " try:\n", - " print(\"\\n--- Load features into online store/materialize_incremental ---\")\n", - " feature_views= store.list_feature_views()\n", - " if not feature_views:\n", - " raise PermissionError(\"No access to feature-views or no feature-views available.\")\n", - " store.materialize_incremental(end_date=datetime.now())\n", - " except PermissionError as pe:\n", - " print(f\"Permission error: {pe}\")\n", - " except Exception as e:\n", - " print(f\"An occurred while performing materialize incremental: {e}\")\n", - "\n", - " print(\"\\n--- Online features ---\")\n", - " fetch_online_features(store)\n", - "\n", - " print(\"\\n--- Online features retrieved (instead) through a feature service---\")\n", - " fetch_online_features(store, source=\"feature_service\")\n", - "\n", - " print(\n", - " \"\\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---\"\n", - " )\n", - " fetch_online_features(store, source=\"push\")\n", - "\n", - " print(\"\\n--- Simulate a stream event ingestion of the hourly stats df ---\")\n", - " event_df = pd.DataFrame.from_dict(\n", - " {\n", - " \"driver_id\": [1001],\n", - " \"event_timestamp\": [datetime.now()],\n", - " \"created\": [datetime.now()],\n", - " \"conv_rate\": [1.0],\n", - " \"acc_rate\": [1.0],\n", - " \"avg_daily_trips\": [1000],\n", - " }\n", - " )\n", - " store.push(\"driver_stats_push_source\", event_df, to=PushMode.ONLINE_AND_OFFLINE)\n", - "\n", - " print(\"\\n--- Online features again with updated values from a stream push---\")\n", - " fetch_online_features(store, source=\"push\")\n", - "\n", - "except Exception as e:\n", - " print(f\"An error occurred: {e}\")\n" + "# Run the permission check function\n", + "check_permissions()\n" ], "id": "14b7ad38368db767", "outputs": [ @@ -426,92 +458,87 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running Feast RBAC test for Read only User...\r\n", "\n", - "--- Historical features for training ---\n", - "Handling connection for 8083\n", + "--- List feature views ---\n", + "Successfully listed 2 feature views:\n", + " - driver_hourly_stats\n", + " - driver_hourly_stats_fresh\n", + "\n", + "--- Fetching Historical Features for Training ---\n", "Handling connection for 8081\n", - " driver_id event_timestamp label_driver_reported_satisfaction \\\n", + "Successfully fetched training historical features:\n", + " driver_id event_timestamp label_driver_reported_satisfaction \\\n", "0 1001 2021-04-12 10:59:42+00:00 1 \n", "1 1002 2021-04-12 08:12:10+00:00 5 \n", "2 1003 2021-04-12 16:40:26+00:00 3 \n", "\n", " val_to_add val_to_add_2 conv_rate acc_rate avg_daily_trips \\\n", - "0 1 10 0.568053 0.225828 614 \n", - "1 2 20 0.449785 0.860379 686 \n", - "2 3 30 0.153563 0.300467 725 \n", + "0 1 10 0.677818 0.453707 193 \n", + "1 2 20 0.328160 0.900565 929 \n", + "2 3 30 0.787191 0.958963 571 \n", "\n", " conv_rate_plus_val1 conv_rate_plus_val2 \n", - "0 1.568053 10.568053 \n", - "1 2.449785 20.449785 \n", - "2 3.153563 30.153563 \n", + "0 1.677818 10.677818 \n", + "1 2.328160 20.328160 \n", + "2 3.787191 30.787191 \n", "\n", - "--- Historical features for batch scoring ---\n", + "--- Fetching Historical Features for Batch Scoring ---\n", "Handling connection for 8081\n", - " driver_id event_timestamp \\\n", - "0 1002 2025-03-05 19:08:29.434969+00:00 \n", - "1 1001 2025-03-05 19:08:29.434969+00:00 \n", - "2 1003 2025-03-05 19:08:29.434969+00:00 \n", + "Successfully fetched batch scoring historical features:\n", + " driver_id event_timestamp \\\n", + "0 1001 2025-03-06 20:16:15.556223+00:00 \n", + "1 1002 2025-03-06 20:16:15.556223+00:00 \n", + "2 1003 2025-03-06 20:16:15.556223+00:00 \n", "\n", " label_driver_reported_satisfaction val_to_add val_to_add_2 conv_rate \\\n", - "0 5 2 20 0.806965 \n", - "1 1 1 10 0.432434 \n", - "2 3 3 30 0.295015 \n", + "0 1 1 10 0.782836 \n", + "1 5 2 20 0.731948 \n", + "2 3 3 30 0.613211 \n", "\n", " acc_rate avg_daily_trips conv_rate_plus_val1 conv_rate_plus_val2 \n", - "0 0.290849 144 2.806965 20.806965 \n", - "1 0.076731 357 1.432434 10.432434 \n", - "2 0.962840 733 3.295015 30.295015 \n", + "0 0.729726 652 1.782836 10.782836 \n", + "1 0.384902 902 2.731948 20.731948 \n", + "2 0.075386 101 3.613211 30.613211 \n", "\n", - "--- Load features into online store/materialize_incremental ---\n", - "Materializing \u001B[1m\u001B[32m2\u001B[0m feature views to \u001B[1m\u001B[32m2025-03-05 14:08:30-05:00\u001B[0m into the \u001B[1m\u001B[32mremote\u001B[0m online store.\n", + "--- Write to Feature Store ---\n", "\n", - "\u001B[1m\u001B[32mdriver_hourly_stats_fresh\u001B[0m from \u001B[1m\u001B[32m2025-03-04 14:08:30-05:00\u001B[0m to \u001B[1m\u001B[32m2025-03-05 14:08:30-05:00\u001B[0m:\n", - "Handling connection for 8081\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - " 0%| | 0/5 [00:00 03-uninstall.ipynb", + "source": "[Next: Uninstall the Operator and all Feast objects](./03-uninstall.ipynb)", "id": "38c54e92643e0bda" } ], diff --git a/examples/operator-rbac/permissions_apply.py b/examples/operator-rbac/permissions_apply.py index 9f5e6c9a370..0d46ad5260a 100644 --- a/examples/operator-rbac/permissions_apply.py +++ b/examples/operator-rbac/permissions_apply.py @@ -1,21 +1,27 @@ +# Necessary modules for permissions and policies in Feast for RBAC from feast.feast_object import ALL_RESOURCE_TYPES from feast.permissions.action import READ, AuthzedAction, ALL_ACTIONS from feast.permissions.permission import Permission from feast.permissions.policy import RoleBasedPolicy -admin_roles = ["feast-writer"] -user_roles = ["feast-reader"] +# Define K8s roles same as created with FeatureStore CR +admin_roles = ["feast-writer"] # Full access (can create, update, delete ) Feast Resources +user_roles = ["feast-reader"] # Read-only access on Feast Resources +# User permissions (feast_user_permission) +# - Grants read and describing Feast objects access user_perm = Permission( name="feast_user_permission", types=ALL_RESOURCE_TYPES, policy=RoleBasedPolicy(roles=user_roles), - actions=[AuthzedAction.DESCRIBE] + READ + actions=[AuthzedAction.DESCRIBE] + READ # Read access (READ_ONLINE, READ_OFFLINE) + describe other Feast Resources. ) +# Admin permissions (feast_admin_permission) +# - Grants full control over all resources admin_perm = Permission( name="feast_admin_permission", types=ALL_RESOURCE_TYPES, policy=RoleBasedPolicy(roles=admin_roles), - actions=ALL_ACTIONS + actions=ALL_ACTIONS # Full permissions: CREATE, UPDATE, DELETE, READ, WRITE )