Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa | ||
|
wiki2:oso [2021/08/09 10:25] alfred [RBAC] |
wiki2:oso [2021/08/09 13:29] (actual) |
||
|---|---|---|---|
| Línea 68: | Línea 68: | ||
| Usa una sintaxis declarativa, en vez de imperativa. Es decir, se le va indicando "qué aceptar" mediante reglas. Sigue un estilo lógico que sustituye el flujo (loops, condicionales...) por "facts" o reglas. En sí, el conjunto de estas reglas es conocido como base de conocimiento. | Usa una sintaxis declarativa, en vez de imperativa. Es decir, se le va indicando "qué aceptar" mediante reglas. Sigue un estilo lógico que sustituye el flujo (loops, condicionales...) por "facts" o reglas. En sí, el conjunto de estas reglas es conocido como base de conocimiento. | ||
| - | Se pueden añadir tantas funciones\reglas con el mismo nombre como se quiera, no se sobreescriben sino que se añaden. | + | Se pueden añadir tantas funciones\reglas con el mismo nombre como se quiera, no se sobreescriben sino que se añaden. Es decir, una consulta será cierta si hace match con una o varias reglas. |
| - | La regla principal y que ha de existir es ''allow(actor, acción, documento)''. | + | La regla principal y que ha de existir es ''allow(actor, acción, recurso)''. |
| ==== Simple Polar rules ==== | ==== Simple Polar rules ==== | ||
| + | Si no se añade un if entonces se acepta cualquier combinación. La siguiente regla acepta todo en todo: | ||
| + | <code> | ||
| + | allow(actor, action, resource); | ||
| + | </code> | ||
| + | |||
| + | Permitir explícitamente a Zora leer el documento 1: | ||
| + | <code> | ||
| + | allow("Zora", "read", "document-1"); | ||
| + | </code> | ||
| + | |||
| Si el usuario es el dueño del gasto: | Si el usuario es el dueño del gasto: | ||
| <code> | <code> | ||
| Línea 87: | Línea 97: | ||
| allow(user: User, "read", _expense: Expense) if | allow(user: User, "read", _expense: Expense) if | ||
| user_in_role(user, "accountant"); | user_in_role(user, "accountant"); | ||
| + | </code> | ||
| + | |||
| + | ==== Sintaxis ==== | ||
| + | Chequear si una variable (actor) matchea con un valor concreto: | ||
| + | <code> | ||
| + | allow(actor, "read", "document-1") if | ||
| + | actor = "Abagail" or | ||
| + | actor = "Carol" or | ||
| + | actor = "Johann"; | ||
| + | </code> | ||
| + | |||
| + | La comparación pasa cuando se usa el ''=''. Donde un parámetro se asigna ese valor y luego se compara. Esta comparación\asignación se puede dividir usando dos operadores diferentes, uno para asignar '':='' y otro para consultar ''==''. | ||
| + | |||
| + | Se puede hacer una query de los valores internos de un objeto. Por ejemplo, para permitir la lectura a un usuario administrador con id 0 al documento con id 1: | ||
| + | <code> | ||
| + | allow(User{id: 0, admin: true}, "read", Document{id: 1, owner: 0}) | ||
| + | </code> | ||
| + | |||
| + | O acceder a sus propiedades: | ||
| + | <code> | ||
| + | # A user that is an administrator may read any document. | ||
| + | allow(user, "read", _document) if | ||
| + | user.admin = true; | ||
| + | </code> | ||
| + | |||
| + | Para **comprobar el tipado** usamos matches: | ||
| + | <code> | ||
| + | # A user that is an administrator may read any document. | ||
| + | allow(user, "read", _document) if | ||
| + | user matches User and | ||
| + | user.admin = true; | ||
| + | |||
| + | # A user may read any document that they own. | ||
| + | allow(user, "read", document) if | ||
| + | user matches User and | ||
| + | document matches Document and | ||
| + | user.id = document.owner; | ||
| + | | ||
| + | # ----- O lo que es lo mismo ---------------------- | ||
| + | |||
| + | # A user that is an administrator may read any document. | ||
| + | allow(user: User, "read", _document: Document) if | ||
| + | user.admin = true; | ||
| + | |||
| + | # A user may read any document that they own. | ||
| + | allow(user: User, "read", document: Document) if | ||
| + | user.id = document.owner; | ||
| + | |||
| + | </code> | ||
| + | |||
| + | Podemos incluso **matchear con los atributos**, algo al estilo de... | ||
| + | <code> | ||
| + | # A user that is an administrator may read any document. | ||
| + | allow(_user: User{admin: true}, "read", _document: Document); | ||
| + | |||
| + | # A user may read any document that they own. | ||
| + | allow(_user: User{id: user_id}, "read", _document: Document{owner: document_owner}) if | ||
| + | user_id = document_owner; | ||
| + | | ||
| + | # A user may read any document that they own. | ||
| + | allow(_user: User{id: user_id}, "read", _document: Document{owner: user_id}); | ||
| + | </code> | ||
| + | |||
| + | Podemos también llamar a métodos: | ||
| + | <code> | ||
| + | allow(_actor, action: String, _resource) if | ||
| + | action.endswith("::resource"); | ||
| + | </code> | ||
| + | |||
| + | Piensa que podemos llamar también reglas con atributos que no tenemos en esta: | ||
| + | <code> | ||
| + | allow(user, action, resource) if | ||
| + | resource_role_applies_to(resource, role_resource) and | ||
| + | user_in_role(user, role, role_resource) and | ||
| + | role_allow(role, action, resource); | ||
| + | </code> | ||
| + | |||
| + | ==== Equivalencias en Python ==== | ||
| + | |||
| + | * None -> nil | ||
| + | * int -> Integer | ||
| + | * float -> Float | ||
| + | * bool -> Boolean | ||
| + | * list -> List | ||
| + | * dict -> Dictionary | ||
| + | * str -> String | ||
| + | |||
| + | Queries en listas o iterables (con yield): | ||
| + | <code> | ||
| + | allow(actor, _action, _resource) if "payroll" in actor.get_groups(); | ||
| + | allow(actor, _action, _resource) if actor.groups.index("HR") == 0; | ||
| + | </code> | ||
| + | |||
| + | Diccionarios: | ||
| + | <code> | ||
| + | # diccionario: user.roles = {"project1": "admin"} | ||
| + | allow(actor, _action, _resource) if actor.roles.project1 = "admin"; | ||
| + | </code> | ||
| + | |||
| + | Podemos acceder a métodos estáticos si la clase ha sido registrada. Por ejemplo, para permitir el acceso total en entorno de desarrollo: | ||
| + | <code> | ||
| + | # En Python | ||
| + | class Env: | ||
| + | @staticmethod | ||
| + | def var(variable): | ||
| + | return os.environ[variable] | ||
| + | | ||
| + | # En Polar | ||
| + | allow(_actor, _action, _resource) if Env.var("ENV") = "development"; | ||
| </code> | </code> | ||
| ===== Cómo implementar... ===== | ===== Cómo implementar... ===== | ||
| ==== RBAC ==== | ==== RBAC ==== | ||
| - | === Básico === | + | |
| Se necesita que se den tres cosas para activar los roles: | Se necesita que se den tres cosas para activar los roles: | ||
| - Añadir configuración de roles y recursos en nuestros ficheros. | - Añadir configuración de roles y recursos en nuestros ficheros. | ||
| - Usar ''role_allows'' en alguna de nuestras reglas. | - Usar ''role_allows'' en alguna de nuestras reglas. | ||
| - Asignar roles a usuarios. | - Asignar roles a usuarios. | ||
| + | |||
| + | Los roles están limitados a los recursos y son un conjunto de acciones permitidas. Para definirlos usamos la regla ''resource'': | ||
| + | |||
| + | <code> | ||
| + | resource(_type: Org, "org", actions, roles) if | ||
| + | actions = ["read", "create_repo"] and | ||
| + | roles = { | ||
| + | member: { | ||
| + | permissions: ["read"], | ||
| + | }, | ||
| + | owner: { | ||
| + | permissions: ["read", "create_repo"], | ||
| + | } | ||
| + | }; | ||
| + | | ||
| + | # que es lo mismo que.... | ||
| + | |||
| + | resource( _type: Org, "org", ["read", "create_repo"], | ||
| + | { | ||
| + | member: { | ||
| + | permissions: ["read"], | ||
| + | }, | ||
| + | owner: { | ||
| + | permissions: ["read", "create_repo"], | ||
| + | } | ||
| + | } | ||
| + | ); | ||
| + | |||
| + | </code> | ||
| + | |||
| + | Para usar un modelo de datos propio se ha de indicar una regla que devuelva si un actor tiene un rol concreto para un recurso concreto. Esta podría ser algo así: | ||
| + | <code> | ||
| + | actor_has_role_for_resource(actor, role_name, resource) if | ||
| + | role in actor.get_roles() and | ||
| + | role_name = role.name and | ||
| + | resource = role.resource; | ||
| + | </code> | ||
| + | |||
| + | === Básico === | ||
| El siguiente fichero Polar indica que cualquier actor con el rol //guest// puede leer una página, pero olo actores con el rol //admin// pueden escribirla. | El siguiente fichero Polar indica que cualquier actor con el rol //guest// puede leer una página, pero olo actores con el rol //admin// pueden escribirla. | ||
| Línea 130: | Línea 288: | ||
| La regla ''recurso'' decide sobre un recurso específico en el sistema. Por ejemplo una página, una ruta o un registro. En el ejemplo ''Page'' puede tener dos acciones ''read'' y ''write''. Repartidas en ''guest'' y ''admin''. Cuando en un rol, a parte de los permissions, se le añade ''implies'' este "hereda" del rol indicado. | La regla ''recurso'' decide sobre un recurso específico en el sistema. Por ejemplo una página, una ruta o un registro. En el ejemplo ''Page'' puede tener dos acciones ''read'' y ''write''. Repartidas en ''guest'' y ''admin''. Cuando en un rol, a parte de los permissions, se le añade ''implies'' este "hereda" del rol indicado. | ||
| + | |||
| + | Los roles pueden implicar otros roles de otros recursos, por ejemplo ''repo:reader'' y ''repo:admin'' de la siguiente regla: | ||
| + | <code> | ||
| + | resource(_type: Org, "org", actions, roles) if | ||
| + | actions = ["read", "create_repos", "list_repos", | ||
| + | "create_role_assignments", "list_role_assignments", "update_role_assignments", "delete_role_assignments"] and | ||
| + | roles = { | ||
| + | member: { | ||
| + | permissions: ["read", "list_repos", "list_role_assignments"], | ||
| + | implies: ["repo:reader"] | ||
| + | }, | ||
| + | owner: { | ||
| + | permissions: ["create_repos", "create_role_assignments", "update_role_assignments", "delete_role_assignments"], | ||
| + | implies: ["member", "repo:admin"] | ||
| + | } | ||
| + | }; | ||
| + | </code> | ||
| + | |||
| + | === Alternativas === | ||
| + | <code> | ||
| + | # Defining roles for users | ||
| + | user_in_role(_user: User{username: "steve"}, "admin"); | ||
| + | user_in_role(_user: User{username: "leina"}, "admin"); | ||
| + | |||
| + | # Assigning groups of users to the same role | ||
| + | user_in_role(user: User, "admin") if | ||
| + | user.username in ["steve", "leina", "alex", "sam"]; | ||
| + | | ||
| + | # Get role assignment from user | ||
| + | user_in_role(user: User, role) if | ||
| + | role = user.role; | ||
| + | | ||
| + | # Allow the admin role to take any action on any resource | ||
| + | role_allow("admin", _action, _resource); | ||
| + | |||
| + | # Allow the member role to read and write to any blog post | ||
| + | role_allow("member", action: String, _resource: BlogPost) if | ||
| + | action in ["read", "write"]; | ||
| + | </code> | ||
| + | |||
| + | |||