def _create_storage(): # Here we instantiate the Policy Storage. # In this case it's Memory or MongoDB Storage, # but we can opt to SQL Storage, any other third-party storage, etc. def create_sql_storage(dsn): engine = create_engine(dsn, echo=True) Base.metadata.create_all(engine) session = scoped_session(sessionmaker(bind=engine)) return SQLStorage(scoped_session=session) print('storage is ', os.environ.get('STORAGE')) use_storage = os.environ.get('STORAGE') if use_storage == 'mongo': user, password, host = 'root', 'root', 'localhost:27017' uri = 'mongodb://%s:%s@%s' % (user, password, host) return MongoStorage(pymongo.MongoClient(host=host), 'vakt_db', collection='vakt_github_guard') elif use_storage == 'mysql': return create_sql_storage( 'mysql+pymysql://root:root@localhost/vakt_db') elif use_storage == 'pg': return create_sql_storage( 'postgresql+psycopg2://postgres:root@localhost/vakt_db') else: return vakt.MemoryStorage()
def _create_storage(): # Here we instantiate the Policy Storage. # In this case it's Memory or MongoDB Storage, # but we can opt to SQL Storage, any other third-party storage, etc. print('st', os.environ.get('STORAGE')) if os.environ.get('STORAGE') == 'mongo': user, password, host = 'root', 'root', 'localhost:27017' uri = 'mongodb://%s:%s@%s' % (user, password, host) return MongoStorage(pymongo.MongoClient(host=host), 'vakt_db', collection='vakt_book_library') else: return vakt.MemoryStorage()
def main(): # configure logger # root = logging.getLogger() # root.setLevel(logging.INFO) # root.addHandler(logging.StreamHandler()) # start server storage = vakt.MemoryStorage() # policy = vakt.Policy.from_json( # '{"actions": [{"py/object": "vakt.rules.operator.Eq", "val": "get"}, {"py/object": "vakt.rules.operator.Eq", "val": "list"}, {"py/object": "vakt.rules.operator.Eq", "val": "read"}], "context": {}, "description": "Grant read access to all states", "effect": "allow", "meta": {}, "resources": [{"id": {"py/object": "vakt.rules.logic.Any"}, "platform": {"py/object": "vakt.rules.operator.Eq", "val": "lib/states"}}], "subjects": [{"py/object": "vakt.rules.operator.Eq", "val": "user:joe"}], "type": 2, "uid": "7d8b335b-9ee8-420d-94e0-ef17e3b92b15"}') # storage.add(p) for p in policies: # print(f"adding p: {p}") # print(p.to_json()) storage.add(p) # print(f"references: {storage.get_all(100, 0)[0]}") guard = vakt.Guard(storage, vakt.RulesChecker()) # inq = vakt.Inquiry(action='get', # resource={'platform': 'lib/states', 'id': '*'}, # subject={'name': 'larry', 'role': 'admin'}, # context={'referer': 'https://github.com'}) # # print(f"get - larry - admin - * - {bool(guard.is_allowed(inq))}") # # inq = vakt.Inquiry(action='edit', # resource={'platform': 'lib/states', 'id': 'one'}, # subject={'name': 'larry', 'role': 'admin'}, # context={'referer': 'https://github.com'}) # # print(f"edit - larry - admin - one - {bool(guard.is_allowed(inq))}") # inq = vakt.Inquiry(action='get', resource={'platform': 'lib/states', 'id': '*'}, subject='user:joe', context={'referer': 'https://github.com'}) print(f"get - * - user___joe - {bool(guard.is_allowed(inq))}") roles = ['one', 'two'] inq = vakt.Inquiry(action='get', resource={'platform': 'lib/states', 'id': 'one'}, subject='user:joe', context={'referer': 'https://github.com'}) print(f"get - one - user___joe - {bool(guard.is_allowed(inq))}") roles = ['one', 'two']
return '=' @property def end_tag(self): """Policy expression end tag""" return '=' # You can use custom Rules in any Policy's context. policy1 = CustomTagsPolicy(uid=1, description='some custom policy', subjects=('=[FGH]+[\w]+=', 'Max'), context={'secret': Between(10, 100)}) policy2 = vakt.Policy(uid=2, description='some default policy', context={'secret': Between(1, 15)}) # You can add custom policies and default ones. storage = vakt.MemoryStorage() storage.add(policy1) storage.add(policy2) # You can use to_json() on custom and default policies. data = list(map(lambda x: x.to_json(), storage.get_all(2, 0))) print(data) # You can use from_json() on custom and default policies. data = list(map(vakt.policy.Policy.from_json, data)) print(data)
class Permissions(YomboLibrary, LibraryDBParentMixin, LibrarySearchMixin): """ Manages all commands available for devices. All modules already have a predefined reference to this library as `self._Commands`. All documentation will reference this use case. """ permissions: ClassVar[dict] = {} # store policy to role mapping auth_platforms: ClassVar[dict] = {} vakt_storage: ClassVar = vakt.MemoryStorage() # The remaining attributes are used by various mixins. _storage_primary_field_name: ClassVar[str] = "permission_id" _class_storage_yombo_toml_section: str = "rbac_permissions" # what section of the config to use _storage_label_name: ClassVar[str] = "permission" _storage_class_reference: ClassVar = Permission _storage_schema: ClassVar = PermissionSchema() _storage_attribute_name: ClassVar[str] = "permissions" _storage_search_fields: ClassVar[List[str]] = [ "permission_id", "machine_label", "label" ] _storage_attribute_sort_key: ClassVar[str] = "created_at" _storage_attribute_sort_key_order: ClassVar[str] = "desc" _new_items_require_authentication: ClassVar[bool] = True @inlineCallbacks def _init_(self, **kwargs): """ Define the base permissions """ yield self.load_from_database() self.guard = vakt.Guard(self.vakt_storage, vakt.RulesChecker()) self.auth_platforms = deepcopy(AUTH_PLATFORMS) # Possible authentication platforms and their actions. self.system_seed = self._Configs.get("core.rand_seed") @inlineCallbacks def _load_(self, **kwargs): results = yield global_invoke_all("_auth_platforms_", called_by=self) logger.debug("_auth_platforms_ results: {results}", results=results) for component, platforms in results.items(): for machine_label, platform_data in platforms.items(): if "actions" not in platform_data: logger.warn("Unable to add auth platform, actions is missing from: {component} - {machine_label}", component=component, machine_label=machine_label) continue if "possible" not in platform_data["actions"]: logger.warn("Unable to add auth platform, 'possible' actions are missing from:" " {component} - {machine_label}", component=component, machine_label=machine_label) continue if "user" not in platform_data["actions"]: logger.info("'user' default allowed actions is missing from {component} - {machine_label}," " setting to none.", component=component, machine_label=machine_label) platform_data["actions"]["user"] = [] self.auth_platforms[machine_label] = platform_data yield self.setup_system_permissions() @inlineCallbacks def setup_system_permissions(self): Roles = self._Roles # print(f'permissions - init - roles: {Roles.roles}') # define admin # print("11 permissions, about to define system admin.") permission_id = self._Hash.sha224_compact(f"auto-admin-{self.system_seed}") if permission_id not in self.permissions: # print(f"creating admin permission: {permission_id}") role = Roles.get("admin") yield self.new( role, machine_label="admin", label="Administrator", description="Grant access to everything for admins.", actions=[Any()], resources=[{"platform": Any(), "id": Any()}], # subjects=[f"role:{role.role_id}"], permission_id=permission_id, effect=vakt.ALLOW_ACCESS, request_by="permissions", request_by_type="library", request_context="setup_system_permissions", load_source="local", ) for platform, data in AUTH_PLATFORMS.items(): platform_parts = platform.split(".") # define platform admins platform_machine_label = f"{platform_parts[0]}_{platform_parts[1]}_{platform_parts[2]}_admin".lower() platform_label = f"{platform_parts[2]} {platform_parts[1]} {platform_parts[0]}" permission_id = self._Hash.sha224_compact(f"admin-{platform_machine_label} {self.system_seed}") role_id = self._Hash.sha224_compact(permission_id) try: role = self._Roles.get_advanced({"role_id": role_id, "machine_label": platform_machine_label}, multiple=False) except KeyError: # print(f'data["actions"]["possible"]: {data["actions"]["possible"]}') actions_string = ", ".join(data["actions"]["possible"]) description = f"Admin access to '{platform_label}', actions: {actions_string}" role = yield self._Roles.new( role_id=role_id, machine_label=platform_machine_label, label=f"{platform_label} admin", description=description, request_by="permissions", request_by_type="library", request_context="setup_system_permissions", load_source="local", ) # print(f"22 permissions, about to define platform admin: {platform_label}") if permission_id not in self.permissions: yield self.new( role, machine_label=platform_machine_label, label=f"{platform_label} admin", description=description, actions=data["actions"]["possible"], resources=[{"platform": Eq(platform), "id": Any()}], # subjects=[Eq(f"role:{role.role_id}")], permission_id=permission_id, effect=vakt.ALLOW_ACCESS, request_by="permissions", request_by_type="library", request_context="setup_system_permissions", load_source="local", ) # else: # self.attach_ # define platform actions for more fine grained controls for action in data["actions"]["possible"]: platform_machine_label = f"{platform_parts[0]}_{platform_parts[1]}_{platform_parts[2]}_{action}".lower() platform_label = f"{platform_parts[0]} {platform_parts[1]} {platform_parts[2]}" permission_id = self._Hash.sha224_compact(f"action-{platform_machine_label} {action} {self.system_seed}") role_id = self._Hash.sha224_compact(permission_id) try: role = self._Roles.get_advanced({"role_id": role_id, "machine_label": platform_machine_label}, multiple=False) except KeyError: description = f"Allow '{platform_label}', action: {action}" role = yield self._Roles.new( role_id=role_id, label=f"{platform_label} {action}", machine_label=platform_machine_label, description=description, request_by="permissions", request_by_type="library", request_context="setup_system_permissions", load_source="local" ) if permission_id not in self.permissions: yield self.new( role, machine_label=platform_machine_label, label=f"{platform_label} {action}", description=description, actions=[action], resources=[{"platform": Eq(platform), "id": Any()}], # subjects=[f"role:{role.role_id}"], permission_id=permission_id, effect=vakt.ALLOW_ACCESS, request_by="permissions", request_by_type="library", request_context="setup_system_permissions", load_source="local" ) # else: # attach.... # Grant all users basic access rights. This can be revoked using new policies to negate this. platform_machine_label = f"{platform_parts[0]}_{platform_parts[1]}_{platform_parts[2]}_user".lower() platform_label = f"{platform_parts[2]} {platform_parts[1]} {platform_parts[0]}" permission_id = self._Hash.sha224_compact(f"user-{platform_machine_label} {self.system_seed}") if permission_id not in self.permissions: if len(data["actions"]["user"]): role = Roles.get("users") actions_string = ", ".join(data["actions"]["user"]) # print(f"44 permissions, about to define platform user: {platform_machine_label} - {actions_string}") yield self.new( role, machine_label=platform_machine_label, label=platform_label, description=f"All users access to '{platform_machine_label}', actions: {actions_string}", actions=data["actions"]["user"], resources=[{"platform": Eq(platform), "id": Any()}], # subjects=[Eq(f"role:{role.role_id}")], permission_id=permission_id, effect=vakt.ALLOW_ACCESS, request_by="permissions", request_by_type="library", request_context="setup_system_permissions", load_source="local" ) @inlineCallbacks def new(self, attachment: Type[AuthMixin], machine_label: str, label: str, description: str, actions, resources, subjects: Optional[list] = None, effect: Union[None, vakt.ALLOW_ACCESS, vakt.DENY_ACCESS] = None, request_by: Optional[str] = None, request_by_type: Union[None, str] = None, request_context: Optional[str] = None, authentication: Optional[Type[AuthMixin]] = None, permission_id=None, load_source=None): """ Create a new permission and attach to a role, user, or authkey. To track how the permission was created, either request_by and request_by_type or an authentication item can be anything with the authmixin, such as a user, websession, or authkey. :param attachment: A role, authkey, or user. :param machine_label: Permission machine_label :param label: Permission human label. :param description: Permission description. :param actions: :param resources: :param subjects: :param effect: :param request_by: Who created the permission. "alexamodule" :param request_by_type: What type of item created it: "module" :param request_context: Some additional information about where the request comes from. :param authentication: An auth item such as a websession or authkey. :param permission_id: permission the role was loaded. :param load_source: How the permission was loaded. :return: """ _request_by, _request_by_type = self.request_by_info(authentication, request_by, request_by_type) if permission_id is None: permission_id = random_string(length=38) # else: # permission_id = self._Hash.sha224_compact(f"{_request_by}:{_request_by_type}:{machine_label}:{permission_id}") try: found = self.get_advanced({"machine_label": machine_label, "permission_id": permission_id}, multiple=False) raise YomboWarning(f"Found a matching permission: {found.machine_label} - {found.label}") except KeyError: pass if effect in (None, True, 1, 'allow'): effect = vakt.ALLOW_ACCESS else: effect = vakt.DENY_ACCESS if subjects is None: subjects = [Eq(f"{attachment.auth_type}:{attachment.auth_id}")] else: subjects = self.convert_items_to_vakt(subjects) policy = vakt.Policy( permission_id, actions=self.convert_items_to_vakt(actions), resources=self.convert_items_to_vakt(resources), subjects=subjects, effect=effect, # context=self.convert_items_to_vakt(context), description=description, ).to_json() if label is None: label = machine_label if description is None: description = label results = yield self.load_an_item_to_memory( { "id": permission_id, "attach_id": attachment.auth_id, "attach_type": attachment.auth_type, "policy": policy, "machine_label": machine_label, "label": label, "description": description, "request_by": request_by, "request_by_type": request_by_type, "request_context": request_context, }, authentication=authentication, load_source=load_source, generated_source=caller_string()) return results def delete(self, permssion_id: str) -> None: """ Deletes a permission item. :param permssion_id: Permission id to delete. """ if permssion_id not in self.permissions: raise KeyError("Permission id not found, cannot delete it.") permission = self.permissions[permssion_id] if permission._meta["load_source"] == "library": raise YomboWarning("Unable to delete library created permissions.") try: self.vakt_storage.delete(permssion_id) except: pass del self.permissions[permssion_id] #TODO: delete def find_authentication_item(self, request_by: str, request_by_type: str) -> Type[AuthMixin]: """ Attempts to locate the authentication item using the id and type. """ if request_by_type == AUTH_TYPE_AUTHKEY: if request_by in self._AuthKeys: return self._AuthKeys[request_by] if request_by_type == AUTH_TYPE_USER: if request_by in self._Users: return self._Users[request_by] raise KeyError("Could not find the source auth item.") def request_by_info(self, authentication: Optional[Type["yombo.mixins.auth_mixin.AuthMixin"]] = None, request_by: Optional[str] = None, request_by_type: Optional[str] = None, instance: Optional[Any] = None, default: Optional[Type["yombo.mixins.auth_mixin.AuthMixin"]] = None): """ Extract authentication information from either authentication or request_by and request_by_type fields. :param authentication: :param request_by: :param request_by_type: :param instance: Any instance of an object that can contain request_by and request_by_type attributes. :param default: If no available authentication information is available, use this as a last resort. :return: """ if authentication is not None: return authentication.accessor_id, authentication.accessor_type search = [{ "request_by": request_by, "request_by_type": request_by_type, }] if instance is not None: search.append({ "request_by": instance.request_by, "request_by_type": instance.request_by_type, }) if default is not None: search.append({ "request_by": default.accessor_id, "request_by_type": default.accessor_type, }) return self.search_request_by_info(search) @staticmethod def search_request_by_info(the_items: List[dict]): """ Searches the_items for authentication and returns request_id and request_type. Returns request_by and request_by_type. :param the_items: A list of dictionaries to search. :return: """ for item in the_items: if "request_by" in item and item["request_by"] is not None and \ "request_by_type" in item and item["request_by_type"] is not None: return item["request_by"], item["request_by_type"] raise YomboWarning("Authentication information not found.") def is_allowed(self, platform: str, action: str, item_id: Optional[str] = None, authentication: Type[AuthMixin] = None, request_context: Optional[str] = None, raise_error: Optional[bool] = None): """ Check if the action is allowed for the platform, by subject (who). :param platform: Which resource - yombo.lib.atoms :param action: What's happening - edit, view, delete, etc. :param item_id: Which item is being manipulated. Use "*" for any. :param authentication: Who - either a websession or authkey, or any authentication class instance. :param request_context: Context for the request. Such as source IP, automation rule, etc. :param raise_error: If true, raise YomboNoAccess if no access, this is the default. :return: """ if authentication is None: logger.warn("is_allow got a blank authentication, going to deny because...duhhhh...") return False if item_id is None: item_id = "*" inq = vakt.Inquiry(action=action, resource={'platform': platform, 'id': item_id}, subject=f'user:{authentication.accessor_id}' ) if bool(self.guard.is_allowed(inq)) is True: return True for role_id, role in authentication.roles.items(): inq = vakt.Inquiry(action=action, resource={'platform': platform, 'id': item_id}, subject=f'role:{role_id}' ) if bool(self.guard.is_allowed(inq)) is True: return True if raise_error in (None, True, "1", "yes"): raise YomboNoAccess(action=action, platform=platform, item_id=item_id, request_by=authentication.accessor_id, request_by_type=authentication.accessor_type, request_context=request_context) return False @staticmethod def convert_items_to_vakt(incoming: Any) -> Any: """ Converts items sent in from strings to vakt items. :param incoming: :return: """ def do_convert(input): if isinstance(input, str): if input == "*": return Any() else: return Eq(input) elif isinstance(input, Rule): return input return None items = deepcopy(incoming) if isinstance(items, list): for idx, value in enumerate(items): if isinstance(value, dict): # print("CTTV: doing dict.") for label, data in value.items(): items[idx][label] = do_convert(data) else: # print(f"CTTV: doing single item: {value}") items[idx] = do_convert(value) # print(f"CTTV: DONE doing single item: {items[idx]}") else: items = do_convert(items) return items