{"id":16445349,"url":"https://github.com/lloydmeta/less-is-moar-union_only-perms-model","last_synced_at":"2026-05-02T01:34:01.195Z","repository":{"id":217402613,"uuid":"743765773","full_name":"lloydmeta/less-is-moar-union_only-perms-model","owner":"lloydmeta","description":"Implementing selective subtraction of perms via addition of inverse permissions in the ES Security model","archived":false,"fork":false,"pushed_at":"2024-01-17T06:38:19.000Z","size":10,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-09T01:02:11.205Z","etag":null,"topics":["authorisation","authz","elasticsearch","poc","proof-of-concept","security","union"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lloydmeta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-16T00:03:05.000Z","updated_at":"2024-01-16T00:17:51.000Z","dependencies_parsed_at":"2024-01-16T06:07:40.456Z","dependency_job_id":"5081946d-9eca-4581-a6b8-5bd533fcd592","html_url":"https://github.com/lloydmeta/less-is-moar-union_only-perms-model","commit_stats":null,"previous_names":["lloydmeta/less-is-moar-union_only-perms-model"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydmeta%2Fless-is-moar-union_only-perms-model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydmeta%2Fless-is-moar-union_only-perms-model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydmeta%2Fless-is-moar-union_only-perms-model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydmeta%2Fless-is-moar-union_only-perms-model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lloydmeta","download_url":"https://codeload.github.com/lloydmeta/less-is-moar-union_only-perms-model/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240831199,"owners_count":19864711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorisation","authz","elasticsearch","poc","proof-of-concept","security","union"],"created_at":"2024-10-11T09:44:09.140Z","updated_at":"2026-05-02T01:34:01.159Z","avatar_url":"https://github.com/lloydmeta.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Subtraction via addition in an additive only perms model\n\nAn exploration in how to express \"allow all except for _these_\" perms when the authorisation\nbackend is the ES Security model, which is [unions-only](https://www.elastic.co/guide/en/elasticsearch/reference/current/authorization.html).\n\nThis \"subtracting-via-adding\" could be applied in other similarly-constrained-and-not-uncommon\nbackends, but this repo holds a demo that only interfaces with ES and its Security model.\n\n### Basic premise\n\n* If we make the result of each action permission check for an actor and object a vector (magnitude with direction): that is: 0, 1 or -1, we can “subtract by adding”.\n* If we only have booleans (1 or 0), assuming that there is a `can_do` function that returns `0` if the \"current\" actor does _not_ have permission to perform action\u003csub\u003e0\u003c/sub\u003e on the input object, and `1` otherwise, and it can also do so for an \"inverse\" action, let's say action\u003csub\u003e0\u003c/sub\u003e\u003csup\u003e-1\u003c/sup\u003e if the input actor has been explicitly _disallowed_ from doing action\u003csub\u003e0\u003c/sub\u003e on the input object.\n\n```math\n\\begin{align*}\n\n{authorised\\_total_{actor}}(action_{0..n}, object) \u0026= \\sum_{a=0}^{n}(can\\_do_{actor}(action_0, object)\\text{ } + \\\\\u0026\\text{ }(-1 \\cdot can\\_do_{actor}(action_0^{-1}, object)))\\\\\\\\\n\n\n{authorised}_{actor}(action_{0..n}, object) \u0026= \\begin{cases}\n    1 \u0026 \\text{if } {authorised\\_total_{actor}}(action_{0..n}, object) = n \\\\\n    0 \u0026 \\text{otherwise.}\n\\end{cases}\n\\end{align*}\n```\n\n\u003csub\u003eThere is probably a way to express the above in terms of multiplication, but addition \u003cem\u003efeels\u003c/em\u003e more straightforward a mapping..\u003c/sub\u003e\n\nThe `isAuthorised` function in `poc.sc` demonstrates an implementation of the above building on\n* [Users](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html)\n* [AppPrivileges](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-privileges.html)\n* [Roles](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role.html)\n* [_has_privileges](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-has-privileges.html) call.\n\n## Running the PoC\n\n### Requires\n\n* [Ammonite](https://ammonite.io): must at least support Scala 3 because we're on that no-parens hotness.\n* [Docker CLI](https://www.docker.com/products/cli/): [Rancher Desktop](https://rancherdesktop.io) will do\n\n### Running\n\n1. `make start-es` to bring up ES, wait until it's up before proceeding.\n\n   `✅ Elasticsearch security features have been automatically configured!` should show up\n2. `make repl` in a separate terminal to load up a REPL with the PoC functions loaded in and tested.\n3. Test it out\n   1. Assert that the Support user has blanket permissions to `deployment:edit` a given deployment\n\n      ```scala\n      val deplIdToTest = generateRandStr()\n      isAuthorised(supportEsUser, deplIdToTest -\u003e \"deployment:edit\")\n      // res1: Boolean = true\n      ```\n   2. Subtract the deployment from the user's allowed set by adding it to the _inverse_ support role\n\n      ```scala\n      putEsRole(\n        inverse(supportAppPrivName),\n        Seq(deplIdToTest)\n      )()\n      ```\n   3. Test the access to that deployment again:\n\n      ```scala\n      isAuthorised(supportEsUser, deplIdToTest -\u003e \"deployment:edit\")\n      // res3: Boolean = false\n      ```\n   4. Remove it by emptying the inverse support resource list\n\n      ```scala\n      putEsRole(\n        inverse(supportAppPrivName),\n        Seq.empty\n      )()\n      ```\n   5. Test the access to that deployment again:\n\n      ```scala\n      isAuthorised(supportEsUser, deplIdToTest -\u003e \"deployment:edit\")\n      // res3: Boolean = true\n      ```\n4. Exit the REPL with `ctrl+d` then `ctrl+c`\n5. `make stop-es` to stop and cleanup ES","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flloydmeta%2Fless-is-moar-union_only-perms-model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flloydmeta%2Fless-is-moar-union_only-perms-model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flloydmeta%2Fless-is-moar-union_only-perms-model/lists"}