diff --git a/examples/operator-rbac/.gitignore b/examples/operator-rbac/.gitignore new file mode 100644 index 0000000000..8eb1393fcc --- /dev/null +++ b/examples/operator-rbac/.gitignore @@ -0,0 +1 @@ +/client/feature_repo/feature_store.yaml diff --git a/examples/operator-rbac/README.md b/examples/operator-rbac/README.md new file mode 100644 index 0000000000..2148581504 --- /dev/null +++ b/examples/operator-rbac/README.md @@ -0,0 +1,6 @@ +# Install and run a Feature Store on Kubernetes with the Feast Operator + +The [k8s-rbac.ipynb](k8s-rbac.ipynb) will guide you through how to enable Role-Based Access Control (RBAC) for Feast using [Feast Operator](../../infra/feast-operator/) with the Kubernetes Authentication type. + + + diff --git a/examples/operator-rbac/client/admin_user_resources.yaml b/examples/operator-rbac/client/admin_user_resources.yaml new file mode 100644 index 0000000000..4e7dfab4f4 --- /dev/null +++ b/examples/operator-rbac/client/admin_user_resources.yaml @@ -0,0 +1,50 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: feast-admin-sa + namespace: feast-client +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: feast-admin-rolebinding + namespace: feast +subjects: + - kind: ServiceAccount + name: feast-admin-sa + namespace: feast-client +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: feast-writer +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: client-admin-user + namespace: feast-client + labels: + app: client-admin +spec: + replicas: 1 + selector: + matchLabels: + app: client-admin + template: + metadata: + labels: + app: client-admin + spec: + serviceAccountName: feast-admin-sa + containers: + - name: client-admin-container + image: feastdev/feature-server:latest + imagePullPolicy: Always + command: ["sleep", "infinity"] + volumeMounts: + - name: client-feature-repo-config + mountPath: /opt/app-root/src + volumes: + - name: client-feature-repo-config + configMap: + name: client-feature-repo-config diff --git a/examples/operator-rbac/client/feature_repo/test.py b/examples/operator-rbac/client/feature_repo/test.py new file mode 100644 index 0000000000..b78c01acf2 --- /dev/null +++ b/examples/operator-rbac/client/feature_repo/test.py @@ -0,0 +1,140 @@ +import os +from datetime import datetime + +import pandas as pd +from feast import FeatureStore +from feast.data_source import PushMode + + +def run_demo(): + try: + os.environ["LOCAL_K8S_TOKEN"] = "" + + store = FeatureStore(repo_path=".") + + print("\n--- Historical features for training ---") + fetch_historical_features_entity_df(store, for_batch_scoring=False) + + print("\n--- Historical features for batch scoring ---") + fetch_historical_features_entity_df(store, for_batch_scoring=True) + + try: + print("\n--- Load features into online store/materialize_incremental ---") + feature_views= store.list_feature_views() + if not feature_views: + raise PermissionError("No access to feature-views or no feature-views available.") + store.materialize_incremental(end_date=datetime.now()) + except PermissionError as pe: + print(f"Permission error: {pe}") + except Exception as e: + print(f"An occurred while performing materialize incremental: {e}") + + print("\n--- Online features ---") + fetch_online_features(store) + + print("\n--- Online features retrieved (instead) through a feature service---") + fetch_online_features(store, source="feature_service") + + print( + "\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---" + ) + fetch_online_features(store, source="push") + + print("\n--- Simulate a stream event ingestion of the hourly stats df ---") + event_df = pd.DataFrame.from_dict( + { + "driver_id": [1001], + "event_timestamp": [datetime.now()], + "created": [datetime.now()], + "conv_rate": [1.0], + "acc_rate": [1.0], + "avg_daily_trips": [1000], + } + ) + store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE) + + print("\n--- Online features again with updated values from a stream push---") + fetch_online_features(store, source="push") + + except Exception as e: + print(f"An error occurred: {e}") + + +def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool): + try: + entity_df = pd.DataFrame.from_dict( + { + "driver_id": [1001, 1002, 1003], + "event_timestamp": [ + datetime(2021, 4, 12, 10, 59, 42), + datetime(2021, 4, 12, 8, 12, 10), + datetime(2021, 4, 12, 16, 40, 26), + ], + "label_driver_reported_satisfaction": [1, 5, 3], + # values we're using for an on-demand transformation + "val_to_add": [1, 2, 3], + "val_to_add_2": [10, 20, 30], + + } + + ) + if for_batch_scoring: + entity_df["event_timestamp"] = pd.to_datetime("now", utc=True) + + training_df = store.get_historical_features( + entity_df=entity_df, + features=[ + "driver_hourly_stats:conv_rate", + "driver_hourly_stats:acc_rate", + "driver_hourly_stats:avg_daily_trips", + "transformed_conv_rate:conv_rate_plus_val1", + "transformed_conv_rate:conv_rate_plus_val2", + ], + ).to_df() + print(training_df.head()) + + except Exception as e: + print(f"An error occurred while fetching historical features: {e}") + + +def fetch_online_features(store, source: str = ""): + try: + entity_rows = [ + # {join_key: entity_value} + { + "driver_id": 1001, + "val_to_add": 1000, + "val_to_add_2": 2000, + }, + { + "driver_id": 1002, + "val_to_add": 1001, + "val_to_add_2": 2002, + }, + ] + if source == "feature_service": + features_to_fetch = store.get_feature_service("driver_activity_v1") + elif source == "push": + features_to_fetch = store.get_feature_service("driver_activity_v3") + else: + features_to_fetch = [ + "driver_hourly_stats:acc_rate", + "transformed_conv_rate:conv_rate_plus_val1", + "transformed_conv_rate:conv_rate_plus_val2", + ] + returned_features = store.get_online_features( + features=features_to_fetch, + entity_rows=entity_rows, + ).to_dict() + for key, value in sorted(returned_features.items()): + print(key, " : ", value) + + except Exception as e: + print(f"An error occurred while fetching online features: {e}") + + +if __name__ == "__main__": + try: + run_demo() + except Exception as e: + print(f"An error occurred in the main execution: {e}") diff --git a/examples/operator-rbac/client/readonly_user_resources.yaml b/examples/operator-rbac/client/readonly_user_resources.yaml new file mode 100644 index 0000000000..4acdaa46f3 --- /dev/null +++ b/examples/operator-rbac/client/readonly_user_resources.yaml @@ -0,0 +1,51 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: feast-user-sa + namespace: feast-client +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: feast-user-rolebinding + namespace: feast +subjects: + - kind: ServiceAccount + name: feast-user-sa + namespace: feast-client +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: feast-reader +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: client-readonly-user + namespace: feast-client + labels: + app: client-user +spec: + replicas: 1 + selector: + matchLabels: + app: client-user + template: + metadata: + labels: + app: client-user + spec: + serviceAccountName: feast-user-sa + containers: + - name: client-user-container + image: feastdev/feature-server:latest + imagePullPolicy: Always + command: ["sleep", "infinity"] + volumeMounts: + - name: client-feature-repo-config + mountPath: /opt/app-root/src + volumes: + - name: client-feature-repo-config + configMap: + name: client-feature-repo-config + diff --git a/examples/operator-rbac/client/unauthorized_user_resources.yaml b/examples/operator-rbac/client/unauthorized_user_resources.yaml new file mode 100644 index 0000000000..65cfd44b59 --- /dev/null +++ b/examples/operator-rbac/client/unauthorized_user_resources.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: feast-unauthorized-user-sa + namespace: feast-client +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: client-unauthorized-user + namespace: feast-client + labels: + app: client-unauthorized-user +spec: + replicas: 1 + selector: + matchLabels: + app: client-unauthorized-user + template: + metadata: + labels: + app: client-unauthorized-user + spec: + serviceAccountName: feast-unauthorized-user-sa + containers: + - name: client-unauthorized-user-container + image: feastdev/feature-server:latest + imagePullPolicy: Always + command: ["sleep", "infinity"] + volumeMounts: + - name: client-feature-repo-config + mountPath: /opt/app-root/src + volumes: + - name: client-feature-repo-config + configMap: + name: client-feature-repo-config diff --git a/examples/operator-rbac/k8s-rbac.ipynb b/examples/operator-rbac/k8s-rbac.ipynb new file mode 100644 index 0000000000..ff597a3f08 --- /dev/null +++ b/examples/operator-rbac/k8s-rbac.ipynb @@ -0,0 +1,1108 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Feast Operator with RBAC Configuration\n", + "## Objective\n", + "\n", + "This demo provide a reference implementation of a runbook,how to enable Role-Based Access Control (RBAC) for Feast using [Feast Operator](../../infra/feast-operator/) with the Kubernetes Authentication type. \n", + "The demo steps involve deploying Feast Operator, creating Feast Instances with server components registry, offline, online and client examples within a Kubernetes environment. The goal is to ensure secure access control Feast instances deployed by 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" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "* Kubernetes Cluster\n", + "* [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) Kubernetes CLI tool." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install Prerequisites\n", + "\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", + "```" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:06:41.847975Z", + "start_time": "2025-02-20T20:06:41.547373Z" + } + }, + "source": [ + "!kubectl create ns feast\n", + "!kubectl config set-context --current --namespace feast\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "namespace/feast created\r\n", + "Context \"kind-kind\" modified.\r\n" + ] + } + ], + "execution_count": 91 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Validate the cluster setup:" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:06:53.848611Z", + "start_time": "2025-02-20T20:06:53.688581Z" + } + }, + "source": [ + "!kubectl get ns feast" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME STATUS AGE\r\n", + "feast Active 12s\r\n" + ] + } + ], + "execution_count": 92 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install the Feast Operator" + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:07:12.172579Z", + "start_time": "2025-02-20T20:06:59.649529Z" + } + }, + "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": 93 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "## Deployment Architecture\n", + "The primary objective of this runbook is to guide the deployment of Feast services with RBAC\n", + "\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 client RBAC examples to test Feast RBAC based on roles assigned.\n" + ] + }, + { + "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-02-20T20:07:23.907790Z", + "start_time": "2025-02-20T20:07:23.610702Z" + } + }, + "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": 94 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Validate the running FeatureStore deployment\n", + "Validate the deployment status." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:07:58.940489Z", + "start_time": "2025-02-20T20:07:58.521478Z" + } + }, + "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-pck7t 4/4 Running 0 34s\r\n", + "\r\n", + "NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\r\n", + "service/feast-sample-kubernetes-auth-offline ClusterIP 10.96.82.193 80/TCP 34s\r\n", + "service/feast-sample-kubernetes-auth-online ClusterIP 10.96.21.169 80/TCP 34s\r\n", + "service/feast-sample-kubernetes-auth-registry ClusterIP 10.96.115.81 80/TCP 34s\r\n", + "service/feast-sample-kubernetes-auth-ui ClusterIP 10.96.57.38 80/TCP 34s\r\n", + "\r\n", + "NAME READY UP-TO-DATE AVAILABLE AGE\r\n", + "deployment.apps/feast-sample-kubernetes-auth 1/1 1 1 34s\r\n", + "\r\n", + "NAME DESIRED CURRENT READY AGE\r\n", + "replicaset.apps/feast-sample-kubernetes-auth-774f6df8df 1 1 1 34s\r\n", + "deployment.apps/feast-sample-kubernetes-auth condition met\r\n" + ] + } + ], + "execution_count": 95 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Validate that the FeatureStore CR is in a `Ready` state." + ] + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:08:43.424731Z", + "start_time": "2025-02-20T20:08:43.265678Z" + } + }, + "source": [ + "!kubectl get feast" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME STATUS AGE\r\n", + "sample-kubernetes-auth Ready 80s\r\n" + ] + } + ], + "execution_count": 97 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure the RBAC Permissions\n", + "we have defined permission in `permissions_apply.py`." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:10:01.459387Z", + "start_time": "2025-02-20T20:10:01.339932Z" + } + }, + "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": 98 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:12:52.122382Z", + "start_time": "2025-02-20T20:12:51.746264Z" + } + }, + "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": 99 + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:12:56.442866Z", + "start_time": "2025-02-20T20:12:56.224626Z" + } + }, + "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": 100 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## Apply the Permissions and Feast Object to Registry" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:13:11.931907Z", + "start_time": "2025-02-20T20:13:01.473658Z" + } + }, + "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_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 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": 101 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "List the applied permission details permissions on Feast Resources." + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:32:56.939188Z", + "start_time": "2025-02-20T20:32:14.638882Z" + } + }, + "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-02-20T20:13:11.573999Z'\r\n", + " lastUpdatedTimestamp: '2025-02-20T20:13:11.573999Z'\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-02-20T20:13:11.574883Z'\r\n", + " lastUpdatedTimestamp: '2025-02-20T20:13:11.574883Z'\r\n", + "\r\n" + ] + } + ], + "execution_count": 102 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## Setup & Install Client\n" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The Operator create the client configmap with client feature_store.yaml, we can get it and store into feature_repo " + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:33:09.370639Z", + "start_time": "2025-02-20T20:33:09.069712Z" + } + }, + "cell_type": "code", + "source": [ + "!kubectl get configmap feast-sample-kubernetes-auth-client -n feast -o jsonpath='{.data.feature_store\\.yaml}' > ./client/feature_repo/feature_store.yaml\n", + "!cat client/feature_repo/feature_store.yaml" + ], + "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": 103 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:33:46.530040Z", + "start_time": "2025-02-20T20:33:46.341853Z" + } + }, + "cell_type": "code", + "source": [ + "#create the client namespace\n", + "!kubectl create ns feast-client" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "namespace/feast-client created\r\n" + ] + } + ], + "execution_count": 104 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:34:00.283809Z", + "start_time": "2025-02-20T20:33:59.964348Z" + } + }, + "cell_type": "code", + "source": [ + "!kubectl delete configmap client-feature-repo-config --ignore-not-found -n feast-client\n", + "!kubectl create configmap client-feature-repo-config --from-file=client/feature_repo -n feast-client" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "configmap/client-feature-repo-config created\r\n" + ] + } + ], + "execution_count": 105 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "**Deploy the Client Examples**\n", + "- As an example, we created 3 different users: 1. [admin_user](client/admin_user_resources.yaml), 2. [readonly_user](client/readonly_user_resources.yaml) and 3. [unauthorized_user](client/unauthorized_user_resources.yaml) .\n", + "- Each user is assigned their own service account and roles, as shown in the table below." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:34:21.313519Z", + "start_time": "2025-02-20T20:34:20.630004Z" + } + }, + "cell_type": "code", + "source": [ + "!kubectl apply -f \"client/admin_user_resources.yaml\"\n", + "!kubectl apply -f \"client/readonly_user_resources.yaml\"\n", + "!kubectl apply -f \"client/unauthorized_user_resources.yaml\"" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "serviceaccount/feast-admin-sa created\r\n", + "rolebinding.rbac.authorization.k8s.io/feast-admin-rolebinding created\r\n", + "deployment.apps/client-admin-user created\r\n", + "serviceaccount/feast-user-sa created\r\n", + "rolebinding.rbac.authorization.k8s.io/feast-user-rolebinding created\r\n", + "deployment.apps/client-readonly-user created\r\n", + "serviceaccount/feast-unauthorized-user-sa created\r\n", + "deployment.apps/client-unauthorized-user created\r\n" + ] + } + ], + "execution_count": 106 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "## Validate Client" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "**Run in client-unauthorized-user, unauthorized-user should not able to read or write any resources as no assigned for this user will get the permissions errors as show below.** " + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:35:20.552313Z", + "start_time": "2025-02-20T20:35:10.536799Z" + } + }, + "cell_type": "code", + "source": "!kubectl exec -n feast-client -it $(kubectl get pods -n feast-client -l app=client-unauthorized-user -o jsonpath=\"{.items[0].metadata.name}\") -- python test.py", + "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", + "\r\n", + "--- Historical features for training ---\r\n", + "An error occurred while fetching historical features: Permission error:\r\n", + "Permission feast_admin_permission denied execution of ['DESCRIBE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-writer'],Permission feast_user_permission denied execution of ['DESCRIBE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-reader']\r\n", + "\r\n", + "--- Historical features for batch scoring ---\r\n", + "An error occurred while fetching historical features: Permission error:\r\n", + "Permission feast_admin_permission denied execution of ['DESCRIBE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-writer'],Permission feast_user_permission denied execution of ['DESCRIBE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-reader']\r\n", + "\r\n", + "--- Load features into online store/materialize_incremental ---\r\n", + "Permission error: No access to feature-views or no feature-views available.\r\n", + "\r\n", + "--- Online features ---\r\n", + "An error occurred while fetching online features: Permission error:\r\n", + "Permission feast_admin_permission denied execution of ['DESCRIBE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-writer'],Permission feast_user_permission denied execution of ['DESCRIBE'] to FeatureView:driver_hourly_stats: Requires roles ['feast-reader']\r\n", + "\r\n", + "--- Online features retrieved (instead) through a feature service---\r\n", + "An error occurred while fetching online features: Permission error:\r\n", + "Permission feast_admin_permission denied execution of ['DESCRIBE'] to FeatureService:driver_activity_v1: Requires roles ['feast-writer'],Permission feast_user_permission denied execution of ['DESCRIBE'] to FeatureService:driver_activity_v1: Requires roles ['feast-reader']\r\n", + "\r\n", + "--- Online features retrieved (using feature service v3, which uses a feature view with a push source---\r\n", + "An error occurred while fetching online features: Permission error:\r\n", + "Permission feast_admin_permission denied execution of ['DESCRIBE'] to FeatureService:driver_activity_v3: Requires roles ['feast-writer'],Permission feast_user_permission denied execution of ['DESCRIBE'] to FeatureService:driver_activity_v3: Requires roles ['feast-reader']\r\n", + "\r\n", + "--- Simulate a stream event ingestion of the hourly stats df ---\r\n", + "An error occurred: Unable to find push source 'driver_stats_push_source'.\r\n" + ] + } + ], + "execution_count": 107 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "**Run in client-admin-user. The `admin-user` can perform all actions on all objects.**" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-02-20T20:35:41.127310Z", + "start_time": "2025-02-20T20:35:28.152778Z" + } + }, + "cell_type": "code", + "source": "!kubectl exec -n feast-client -it $(kubectl get pods -n feast-client -l app=client-admin -o jsonpath=\"{.items[0].metadata.name}\") -- python test.py", + "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", + "\r\n", + "--- Historical features for training ---\r\n", + " driver_id ... conv_rate_plus_val2\r\n", + "0 1001 ... 10.224618\r\n", + "1 1002 ... 20.462153\r\n", + "2 1003 ... 30.089775\r\n", + "\r\n", + "[3 rows x 10 columns]\r\n", + "\r\n", + "--- Historical features for batch scoring ---\r\n", + " driver_id ... conv_rate_plus_val2\r\n", + "0 1001 ... 10.786315\r\n", + "1 1002 ... 20.401484\r\n", + "2 1003 ... 30.427706\r\n", + "\r\n", + "[3 rows x 10 columns]\r\n", + "\r\n", + "--- Load features into online store/materialize_incremental ---\r\n", + "Materializing \u001B[1m\u001B[32m2\u001B[0m feature views to \u001B[1m\u001B[32m2025-02-20 20:35:39+00:00\u001B[0m into the \u001B[1m\u001B[32mremote\u001B[0m online store.\r\n", + "\r\n", + "\u001B[1m\u001B[32mdriver_hourly_stats_fresh\u001B[0m from \u001B[1m\u001B[32m2025-02-19 20:35:39+00:00\u001B[0m to \u001B[1m\u001B[32m2025-02-20 20:35:39+00:00\u001B[0m:\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\r\n", + " entity = cls(\r\n", + " 0%| | 0/5 [00:00: 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", + "\r\n", + "--- Historical features for training ---\r\n", + " driver_id ... conv_rate_plus_val2\r\n", + "0 1001 ... 10.224618\r\n", + "1 1002 ... 20.462153\r\n", + "2 1003 ... 30.089775\r\n", + "\r\n", + "[3 rows x 10 columns]\r\n", + "\r\n", + "--- Historical features for batch scoring ---\r\n", + " driver_id ... conv_rate_plus_val2\r\n", + "0 1001 ... 10.786315\r\n", + "1 1002 ... 20.401484\r\n", + "2 1003 ... 30.427706\r\n", + "\r\n", + "[3 rows x 10 columns]\r\n", + "\r\n", + "--- Load features into online store/materialize_incremental ---\r\n", + "Materializing \u001B[1m\u001B[32m2\u001B[0m feature views to \u001B[1m\u001B[32m2025-02-20 20:37:42+00:00\u001B[0m into the \u001B[1m\u001B[32mremote\u001B[0m online store.\r\n", + "\r\n", + "\u001B[1m\u001B[32mdriver_hourly_stats_fresh\u001B[0m from \u001B[1m\u001B[32m2025-02-19 20:37:42+00:00\u001B[0m to \u001B[1m\u001B[32m2025-02-20 20:37:42+00:00\u001B[0m:\r\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\r\n", + " entity = cls(\r\n", + " 0%| | 0/5 [00:00