from typing import Any import click import yaml from bigtree import Node from colorama import Fore, Style from feast import ( BatchFeatureView, Entity, FeatureService, FeatureView, OnDemandFeatureView, StreamFeatureView, utils, ) from feast.cli import cli_utils from feast.cli.cli_options import tagsOption from feast.data_source import DataSource from feast.errors import FeastObjectNotFoundException from feast.permissions.policy import RoleBasedPolicy from feast.repo_operations import create_feature_store from feast.saved_dataset import SavedDataset, ValidationReference @click.group(name="permissions") def feast_permissions_cmd(): """ Access permissions """ pass @feast_permissions_cmd.command(name="list") @click.option( "--verbose", "-v", is_flag=True, help="Print the resources matching each configured permission", ) @tagsOption @click.pass_context def feast_permissions_list_command(ctx: click.Context, verbose: bool, tags: list[str]): from tabulate import tabulate table: list[Any] = [] tags_filter = utils.tags_list_to_dict(tags) store = create_feature_store(ctx) permissions = store.list_permissions(tags=tags_filter) root_node = Node("permissions") roles: set[str] = set() for p in permissions: policy = p.policy if not verbose: cli_utils.handle_not_verbose_permissions_command(p, policy, table) else: if isinstance(policy, RoleBasedPolicy) and len(policy.get_roles()) > 0: roles = set(policy.get_roles()) permission_node = Node( p.name + " " + str(list(roles)), parent=root_node ) else: permission_node = Node(p.name, parent=root_node) for feast_type in p.types: if feast_type in [ FeatureView, OnDemandFeatureView, BatchFeatureView, StreamFeatureView, ]: cli_utils.handle_fv_verbose_permissions_command( feast_type, # type: ignore[arg-type] p, permission_node, store, tags_filter, ) elif feast_type == Entity: cli_utils.handle_entity_verbose_permissions_command( feast_type, # type: ignore[arg-type] p, permission_node, store, tags_filter, ) elif feast_type == FeatureService: cli_utils.handle_fs_verbose_permissions_command( feast_type, # type: ignore[arg-type] p, permission_node, store, tags_filter, ) elif feast_type == DataSource: cli_utils.handle_ds_verbose_permissions_command( feast_type, # type: ignore[arg-type] p, permission_node, store, tags_filter, ) elif feast_type == ValidationReference: cli_utils.handle_vr_verbose_permissions_command( feast_type, # type: ignore[arg-type] p, permission_node, store, tags_filter, ) elif feast_type == SavedDataset: cli_utils.handle_sd_verbose_permissions_command( feast_type, # type: ignore[arg-type] p, permission_node, store, tags_filter, ) if not verbose: print( tabulate( table, headers=[ "NAME", "TYPES", "NAME_PATTERNS", "ACTIONS", "ROLES", "REQUIRED_TAGS", ], tablefmt="plain", ) ) else: cli_utils.print_permission_verbose_example() print("Permissions:") print("") root_node.show() @feast_permissions_cmd.command("describe") @click.argument("name", type=click.STRING) @click.pass_context def permission_describe(ctx: click.Context, name: str): """ Describe a permission """ store = create_feature_store(ctx) try: permission = store.get_permission(name) except FeastObjectNotFoundException as e: print(e) exit(1) print( yaml.dump( yaml.safe_load(str(permission)), default_flow_style=False, sort_keys=False ) ) @feast_permissions_cmd.command(name="check") @click.pass_context def feast_permissions_check_command(ctx: click.Context): """ Validate the permissions configuration """ from tabulate import tabulate all_unsecured_table: list[Any] = [] store = create_feature_store(ctx) permissions = store.list_permissions() objects = cli_utils.fetch_all_feast_objects( store=store, ) print( f"{Style.BRIGHT + Fore.RED}The following resources are not secured by any permission configuration:{Style.RESET_ALL}" ) for o in objects: cli_utils.handle_permissions_check_command( object=o, permissions=permissions, table=all_unsecured_table ) print( tabulate( all_unsecured_table, headers=[ "NAME", "TYPE", ], tablefmt="plain", ) ) all_unsecured_actions_table: list[Any] = [] print( f"{Style.BRIGHT + Fore.RED}The following actions are not secured by any permission configuration (Note: this might not be a security concern, depending on the used APIs):{Style.RESET_ALL}" ) for o in objects: cli_utils.handle_permissions_check_command_with_actions( object=o, permissions=permissions, table=all_unsecured_actions_table ) print( tabulate( all_unsecured_actions_table, headers=[ "NAME", "TYPE", "UNSECURED ACTIONS", ], tablefmt="plain", ) ) @feast_permissions_cmd.command(name="list-roles") @click.option( "--verbose", "-v", is_flag=True, help="Print the resources and actions permitted to each configured role", ) @click.pass_context def feast_permissions_list_roles_command(ctx: click.Context, verbose: bool): """ List all the configured roles """ from tabulate import tabulate table: list[Any] = [] store = create_feature_store(ctx) permissions = store.list_permissions() if not verbose: cli_utils.handler_list_all_permissions_roles( permissions=permissions, table=table ) print( tabulate( table, headers=[ "ROLE NAME", ], tablefmt="grid", ) ) else: objects = cli_utils.fetch_all_feast_objects( store=store, ) cli_utils.handler_list_all_permissions_roles_verbose( objects=objects, permissions=permissions, table=table ) print( tabulate( table, headers=[ "ROLE NAME", "RESOURCE NAME", "RESOURCE TYPE", "PERMITTED ACTIONS", ], tablefmt="plain", ) )