diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/access.md | 53 | ||||
| -rw-r--r-- | docs/openapi.yaml | 180 | ||||
| -rw-r--r-- | docs/trust.md | 2 |
3 files changed, 219 insertions, 16 deletions
diff --git a/docs/access.md b/docs/access.md index 519113c..db86e21 100644 --- a/docs/access.md +++ b/docs/access.md @@ -36,8 +36,8 @@ A swarm gets a registry from its start if its genesis node is given its first us `--bootstrap-users`: it records them, with the swarm ID, in its first change, each with everything (`[]` and `["_continuum"]`). Other users, of its `--htpasswd` or any node's, may do nothing until added (the genesis node warns of those of its `--htpasswd`). -Without it, the swarm has no registry, and is as swarms were before: users all alike -(An existing swarm, below), until someone gives it one. `--protocol-writers` counts in +Without it, the swarm has no registry: its users are all alike (A swarm without a +registry, below), until someone gives it one. `--protocol-writers` counts in both: without a registry, as who may write protocol data; with one, to set it right should nobody be left who may manage users (Keeping the swarm manageable, below). @@ -73,24 +73,47 @@ A user the node lets in but the registry doesn't have may do nothing (`403`). whole swarm (its members, its remakes, its users), so it is given apart, never by accident with the data. A user who may do both has both. -## An existing swarm +## Managing users -A swarm whose document has no registry (one from before it, `_continuum/users` -missing) goes on as before: every user a node lets in may read and write the swarm's -data and use the admin endpoints, and only those in `--protocol-writers` write -protocol data. It gets a registry once a protocol writer writes one, through the store: +Through `/admin/users` ([`openapi.yaml`](openapi.yaml), Users): looking needs `read` on +`_continuum/users`, changing `write` on it. Each change is one change to the document, +logged and named by who made it. `POST /admin/users` adds several at once (below). ```sh -curl -u alice --request PUT --header 'Content-Type: application/json' \ - --data '{"since": 0, "by": "user:alice", "disabled": false, - "caps": {"1": {"path": [], "rights": ["read", "write"]}, - "2": {"path": ["_continuum"], "rights": ["read", "write"]}}}' \ - http://localhost:3000/store/_continuum/users/alice +curl -u alice http://localhost:3000/admin/users # who, and what each may do +curl -u alice -X POST -H 'Content-Type: application/json' \ + --data '{"caps": [{"path": ["servers", "eu"], "rights": ["read"]}]}' \ + http://localhost:3000/admin/users/bob # add bob, reading servers/eu +curl -u alice -X POST -H 'Content-Type: application/json' \ + --data '{"path": [], "rights": ["read", "write"]}' \ + http://localhost:3000/admin/users/bob/caps # grant: answers its ID +curl -u alice -X DELETE http://localhost:3000/admin/users/bob/caps/ID # revoke it +curl -u alice -X PUT -H 'Content-Type: application/json' --data true \ + http://localhost:3000/admin/users/bob/disabled # disable bob ``` -From that write on, every node serves only the users the registry has, so the first -entry must be the writer's own, with `["_continuum"]` (a first write that isn't is -refused, `409`); add the other users the same way. +Disabling a user keeps who wrote what readable; removing it (`DELETE`) doesn't. The +registry is protocol data in the store too, so a user who may write `_continuum/users` +may also edit its entries there directly; the same checks apply. + +## A swarm without a registry + +In a swarm whose document has no registry (`_continuum/users`), started without +`--bootstrap-users`, every user a node lets in may read and write the swarm's data and +use the admin endpoints, and only those in `--protocol-writers` write protocol data. It +gets a registry once a protocol writer adds its users: + +```sh +curl -u alice -X POST -H 'Content-Type: application/json' --data '{ + "alice": {"caps": [{"path": [], "rights": ["read", "write"]}, + {"path": ["_continuum"], "rights": ["read", "write"]}]}, + "bob": {"caps": [{"path": [], "rights": ["read", "write"]}]} + }' http://localhost:3000/admin/users +``` + +From that change on, every node serves only the users the registry has, so the caller +must be among them, with `write` on `_continuum` (else `409`): list every node's users +first. ## Keeping the swarm manageable diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 49e0042..5bf5a50 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -38,6 +38,12 @@ tags: description: | Replacing the swarm's document with a new epoch (compaction, replacement, import), and what happened to local writes that a remake couldn't place. + - name: Users + description: | + The swarm's users and what each may do (docs/access.md): reading needs `read` on + `_continuum/users`, changing `write` on it (in a swarm without a registry, being in + `protocol-writers`). A change that would keep its maker from managing users is + refused (`409`). - name: Debug description: Test hooks, only with `--debug-api`. @@ -727,6 +733,152 @@ paths: '401': $ref: '#/components/responses/Unauthorized' + /admin/users: + get: + tags: [Users] + summary: List the users + responses: + '200': + description: Whether the swarm has a registry, and every user's entry (docs/access.md) with its `name` and whether it may manage users (`manages`). + content: + application/json: + schema: + type: object + properties: + registry: { type: boolean } + users: + type: array + items: { $ref: '#/components/schemas/User' } + '401': + $ref: '#/components/responses/Unauthorized' + '403': + description: May not read `_continuum/users`. + post: + tags: [Users] + summary: Add several users at once + description: | + By name, each with the capabilities `POST /admin/users/{name}` takes; none of + them may exist yet. How a swarm without a registry gets one: then the caller + must be among them, with `write` on `_continuum`. + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: + type: object + properties: + caps: + type: array + items: { $ref: '#/components/schemas/CapabilityRequest' } + example: + alice: { caps: [{ path: [], rights: [read, write] }, { path: [_continuum], rights: [read, write] }] } + bob: { caps: [{ path: [], rights: [read, write] }] } + responses: + '201': { description: Added. } + '400': { description: A name that isn't one, a capability that isn't one, or nobody. } + '401': + $ref: '#/components/responses/Unauthorized' + '403': { description: May not manage users. } + '409': { description: A user exists, or the caller wouldn't manage users in a new registry. } + + /admin/users/{name}: + parameters: + - { name: name, in: path, required: true, schema: { type: string } } + get: + tags: [Users] + summary: One user + responses: + '200': + description: Its entry. + content: + application/json: + schema: { $ref: '#/components/schemas/User' } + '404': { description: No such user. } + post: + tags: [Users] + summary: Add a user + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + caps: + type: array + items: { $ref: '#/components/schemas/CapabilityRequest' } + example: { caps: [{ path: [servers, eu], rights: [read] }] } + responses: + '201': { description: Added. } + '400': { description: Not a user name, or a capability that isn't one. } + '403': { description: May not manage users. } + '409': { description: The user exists. } + delete: + tags: [Users] + summary: Remove a user + description: Disabling it (`PUT …/disabled`) keeps who wrote what readable, and is usually the better choice. + responses: + '204': { description: Removed. } + '403': { description: May not manage users. } + '404': { description: No such user. } + '409': { description: It would keep the caller from managing users. } + + /admin/users/{name}/disabled: + parameters: + - { name: name, in: path, required: true, schema: { type: string } } + put: + tags: [Users] + summary: Disable or enable a user + requestBody: + required: true + content: + application/json: + schema: { type: boolean } + responses: + '204': { description: Done. } + '403': { description: May not manage users. } + '404': { description: No such user. } + '409': { description: It would keep the caller from managing users. } + + /admin/users/{name}/caps: + parameters: + - { name: name, in: path, required: true, schema: { type: string } } + post: + tags: [Users] + summary: Grant a capability + requestBody: + required: true + content: + application/json: + schema: { $ref: '#/components/schemas/CapabilityRequest' } + responses: + '201': + description: Granted, under this ID. + content: + application/json: + schema: + type: object + properties: + id: { type: string } + '400': { description: A capability that isn't one. } + '403': { description: May not manage users. } + '404': { description: No such user. } + + /admin/users/{name}/caps/{id}: + parameters: + - { name: name, in: path, required: true, schema: { type: string } } + - { name: id, in: path, required: true, schema: { type: string } } + delete: + tags: [Users] + summary: Revoke a capability + responses: + '204': { description: Revoked. } + '403': { description: May not manage users. } + '404': { description: No such capability. } + '409': { description: It would keep the caller from managing users. } + /admin/dashboard: get: tags: [Admin] @@ -1485,6 +1637,34 @@ components: $ref: '#/components/schemas/Error' schemas: + CapabilityRequest: + type: object + required: [path, rights] + properties: + path: + type: array + items: { type: string } + description: Segments, as the store's API takes them (`[]`, all of the swarm's data; `["_continuum"]`, protocol data). + rights: + type: array + items: { type: string, enum: [read, write] } + User: + type: object + properties: + name: { type: string } + since: { type: integer, description: Unix ms. } + by: { type: string, description: 'Who added it: `user:<name>`, or a node ID.' } + disabled: { type: boolean } + manages: { type: boolean, description: Whether it may manage users. } + caps: + type: object + additionalProperties: + type: object + properties: + path: { type: array, items: { type: string } } + rights: { type: array, items: { type: string } } + by: { type: string } + since: { type: integer } Error: type: object required: [error] diff --git a/docs/trust.md b/docs/trust.md index bcf9bc7..fd37638 100644 --- a/docs/trust.md +++ b/docs/trust.md @@ -43,7 +43,7 @@ reverse proxy). It listens on `127.0.0.1:3000` by default (`--bind-addr`) and sp plain HTTP: to expose it, put a TLS proxy in front. What each user may do is the swarm's: its user registry, in the document, gives each capabilities by path, to read and to write, protocol data apart ([`access.md`](access.md)); a user the registry doesn't have -may do nothing. In a swarm without a registry (one from before it), all users may read +may do nothing. In a swarm without a registry (started without one), all users may read and write user data and use the admin endpoints, and only those in `--protocol-writers` write protocol data. Epoch metadata and the swarm ID are never writable. The `/debug` endpoints exist only with `--debug-api`. |
