aboutsummaryrefslogtreecommitdiff
path: root/doc/drafts
diff options
context:
space:
mode:
authorAlex <alex@adnab.me>2022-05-24 12:16:39 +0200
committerAlex <alex@adnab.me>2022-05-24 12:16:39 +0200
commit382e74c798263d042b1c6ca3788c866a8c69c4f4 (patch)
tree9bb09e072f553bf1922b6721e5f339ea9e35f3d5 /doc/drafts
parent64c193e3dbb536d5d3c2881bc9aebbb3e4e6272e (diff)
downloadgarage-382e74c798263d042b1c6ca3788c866a8c69c4f4.tar.gz
garage-382e74c798263d042b1c6ca3788c866a8c69c4f4.zip
First version of admin API (#298)
**Spec:** - [x] Start writing - [x] Specify all layout endpoints - [x] Specify all endpoints for operations on keys - [x] Specify all endpoints for operations on key/bucket permissions - [x] Specify all endpoints for operations on buckets - [x] Specify all endpoints for operations on bucket aliases View rendered spec at <https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/admin-api/doc/drafts/admin-api.md> **Code:** - [x] Refactor code for admin api to use common api code that was created for K2V **General endpoints:** - [x] Metrics - [x] GetClusterStatus - [x] ConnectClusterNodes - [x] GetClusterLayout - [x] UpdateClusterLayout - [x] ApplyClusterLayout - [x] RevertClusterLayout **Key-related endpoints:** - [x] ListKeys - [x] CreateKey - [x] ImportKey - [x] GetKeyInfo - [x] UpdateKey - [x] DeleteKey **Bucket-related endpoints:** - [x] ListBuckets - [x] CreateBucket - [x] GetBucketInfo - [x] DeleteBucket - [x] PutBucketWebsite - [x] DeleteBucketWebsite **Operations on key/bucket permissions:** - [x] BucketAllowKey - [x] BucketDenyKey **Operations on bucket aliases:** - [x] GlobalAliasBucket - [x] GlobalUnaliasBucket - [x] LocalAliasBucket - [x] LocalUnaliasBucket **And also:** - [x] Separate error type for the admin API (this PR includes a quite big refactoring of error handling) - [x] Add management of website access - [ ] Check that nothing is missing wrt what can be done using the CLI - [ ] Improve formatting of the spec - [x] Make sure everyone is cool with the API design Fix #231 Fix #295 Co-authored-by: Alex Auvolat <alex@adnab.me> Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/298 Co-authored-by: Alex <alex@adnab.me> Co-committed-by: Alex <alex@adnab.me>
Diffstat (limited to 'doc/drafts')
-rw-r--r--doc/drafts/admin-api.md603
1 files changed, 603 insertions, 0 deletions
diff --git a/doc/drafts/admin-api.md b/doc/drafts/admin-api.md
new file mode 100644
index 00000000..b35a87f1
--- /dev/null
+++ b/doc/drafts/admin-api.md
@@ -0,0 +1,603 @@
+# Specification of Garage's administration API
+
+
+**WARNING.** At this point, there is no comittement to stability of the APIs described in this document.
+We will bump the version numbers prefixed to each API endpoint at each time the syntax
+or semantics change, meaning that code that relies on these endpoint will break
+when changes are introduced.
+
+
+## Access control
+
+The admin API uses two different tokens for acces control, that are specified in the config file's `[admin]` section:
+
+- `metrics_token`: the token for accessing the Metrics endpoint (if this token is not set in the config file, the Metrics endpoint can be accessed without access control);
+- `admin_token`: the token for accessing all of the other administration endpoints (if this token is not set in the config file, access to these endpoints is disabled entirely).
+
+## Administration API endpoints
+
+### Metrics-related endpoints
+
+#### Metrics `GET /metrics`
+
+Returns internal Garage metrics in Prometheus format.
+
+### Cluster operations
+
+#### GetClusterStatus `GET /v0/status`
+
+Returns the cluster's current status in JSON, including:
+
+- ID of the node being queried and its version of the Garage daemon
+- Live nodes
+- Currently configured cluster layout
+- Staged changes to the cluster layout
+
+Example response body:
+
+```json
+{
+ "node": "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f",
+ "garage_version": "git:v0.8.0",
+ "knownNodes": {
+ "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f": {
+ "addr": "10.0.0.11:3901",
+ "is_up": true,
+ "last_seen_secs_ago": 9,
+ "hostname": "node1"
+ },
+ "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff": {
+ "addr": "10.0.0.12:3901",
+ "is_up": true,
+ "last_seen_secs_ago": 1,
+ "hostname": "node2"
+ },
+ "23ffd0cdd375ebff573b20cc5cef38996b51c1a7d6dbcf2c6e619876e507cf27": {
+ "addr": "10.0.0.21:3901",
+ "is_up": true,
+ "last_seen_secs_ago": 7,
+ "hostname": "node3"
+ },
+ "e2ee7984ee65b260682086ec70026165903c86e601a4a5a501c1900afe28d84b": {
+ "addr": "10.0.0.22:3901",
+ "is_up": true,
+ "last_seen_secs_ago": 1,
+ "hostname": "node4"
+ }
+ },
+ "layout": {
+ "version": 12,
+ "roles": {
+ "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f": {
+ "zone": "dc1",
+ "capacity": 4,
+ "tags": [
+ "node1"
+ ]
+ },
+ "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff": {
+ "zone": "dc1",
+ "capacity": 6,
+ "tags": [
+ "node2"
+ ]
+ },
+ "23ffd0cdd375ebff573b20cc5cef38996b51c1a7d6dbcf2c6e619876e507cf27": {
+ "zone": "dc2",
+ "capacity": 10,
+ "tags": [
+ "node3"
+ ]
+ }
+ },
+ "stagedRoleChanges": {
+ "e2ee7984ee65b260682086ec70026165903c86e601a4a5a501c1900afe28d84b": {
+ "zone": "dc2",
+ "capacity": 5,
+ "tags": [
+ "node4"
+ ]
+ }
+ }
+ }
+}
+```
+
+#### ConnectClusterNodes `POST /v0/connect`
+
+Instructs this Garage node to connect to other Garage nodes at specified addresses.
+
+Example request body:
+
+```json
+[
+ "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f@10.0.0.11:3901",
+ "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff@10.0.0.12:3901"
+]
+```
+
+The format of the string for a node to connect to is: `<node ID>@<ip address>:<port>`, same as in the `garage node connect` CLI call.
+
+Example response:
+
+```json
+[
+ {
+ "success": true,
+ "error": null
+ },
+ {
+ "success": false,
+ "error": "Handshake error"
+ }
+]
+```
+
+#### GetClusterLayout `GET /v0/layout`
+
+Returns the cluster's current layout in JSON, including:
+
+- Currently configured cluster layout
+- Staged changes to the cluster layout
+
+(the info returned by this endpoint is a subset of the info returned by GetClusterStatus)
+
+Example response body:
+
+```json
+{
+ "version": 12,
+ "roles": {
+ "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f": {
+ "zone": "dc1",
+ "capacity": 4,
+ "tags": [
+ "node1"
+ ]
+ },
+ "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff": {
+ "zone": "dc1",
+ "capacity": 6,
+ "tags": [
+ "node2"
+ ]
+ },
+ "23ffd0cdd375ebff573b20cc5cef38996b51c1a7d6dbcf2c6e619876e507cf27": {
+ "zone": "dc2",
+ "capacity": 10,
+ "tags": [
+ "node3"
+ ]
+ }
+ },
+ "stagedRoleChanges": {
+ "e2ee7984ee65b260682086ec70026165903c86e601a4a5a501c1900afe28d84b": {
+ "zone": "dc2",
+ "capacity": 5,
+ "tags": [
+ "node4"
+ ]
+ }
+ }
+}
+```
+
+#### UpdateClusterLayout `POST /v0/layout`
+
+Send modifications to the cluster layout. These modifications will
+be included in the staged role changes, visible in subsequent calls
+of `GetClusterLayout`. Once the set of staged changes is satisfactory,
+the user may call `ApplyClusterLayout` to apply the changed changes,
+or `Revert ClusterLayout` to clear all of the staged changes in
+the layout.
+
+Request body format:
+
+```json
+{
+ <node_id>: {
+ "capacity": <new_capacity>,
+ "zone": <new_zone>,
+ "tags": [
+ <new_tag>,
+ ...
+ ]
+ },
+ <node_id_to_remove>: null,
+ ...
+}
+```
+
+Contrary to the CLI that may update only a subset of the fields
+`capacity`, `zone` and `tags`, when calling this API all of these
+values must be specified.
+
+
+#### ApplyClusterLayout `POST /v0/layout/apply`
+
+Applies to the cluster the layout changes currently registered as
+staged layout changes.
+
+Request body format:
+
+```json
+{
+ "version": 13
+}
+```
+
+Similarly to the CLI, the body must include the version of the new layout
+that will be created, which MUST be 1 + the value of the currently
+existing layout in the cluster.
+
+#### RevertClusterLayout `POST /v0/layout/revert`
+
+Clears all of the staged layout changes.
+
+Request body format:
+
+```json
+{
+ "version": 13
+}
+```
+
+Reverting the staged changes is done by incrementing the version number
+and clearing the contents of the staged change list.
+Similarly to the CLI, the body must include the incremented
+version number, which MUST be 1 + the value of the currently
+existing layout in the cluster.
+
+
+### Access key operations
+
+#### ListKeys `GET /v0/key`
+
+Returns all API access keys in the cluster.
+
+Example response:
+
+```json
+[
+ {
+ "id": "GK31c2f218a2e44f485b94239e",
+ "name": "test"
+ },
+ {
+ "id": "GKe10061ac9c2921f09e4c5540",
+ "name": "test2"
+ }
+]
+```
+
+#### CreateKey `POST /v0/key`
+
+Creates a new API access key.
+
+Request body format:
+
+```json
+{
+ "name": "NameOfMyKey"
+}
+```
+
+#### ImportKey `POST /v0/key/import`
+
+Imports an existing API key.
+
+Request body format:
+
+```json
+{
+ "accessKeyId": "GK31c2f218a2e44f485b94239e",
+ "secretAccessKey": "b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835",
+ "name": "NameOfMyKey"
+}
+```
+
+#### GetKeyInfo `GET /v0/key?id=<acces key id>`
+#### GetKeyInfo `GET /v0/key?search=<pattern>`
+
+Returns information about the requested API access key.
+
+If `id` is set, the key is looked up using its exact identifier (faster).
+If `search` is set, the key is looked up using its name or prefix
+of identifier (slower, all keys are enumerated to do this).
+
+Example response:
+
+```json
+{
+ "name": "test",
+ "accessKeyId": "GK31c2f218a2e44f485b94239e",
+ "secretAccessKey": "b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835",
+ "permissions": {
+ "createBucket": false
+ },
+ "buckets": [
+ {
+ "id": "70dc3bed7fe83a75e46b66e7ddef7d56e65f3c02f9f80b6749fb97eccb5e1033",
+ "globalAliases": [
+ "test2"
+ ],
+ "localAliases": [],
+ "permissions": {
+ "read": true,
+ "write": true,
+ "owner": false
+ }
+ },
+ {
+ "id": "d7452a935e663fc1914f3a5515163a6d3724010ce8dfd9e4743ca8be5974f995",
+ "globalAliases": [
+ "test3"
+ ],
+ "localAliases": [],
+ "permissions": {
+ "read": true,
+ "write": true,
+ "owner": false
+ }
+ },
+ {
+ "id": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
+ "globalAliases": [],
+ "localAliases": [
+ "test"
+ ],
+ "permissions": {
+ "read": true,
+ "write": true,
+ "owner": true
+ }
+ },
+ {
+ "id": "96470e0df00ec28807138daf01915cfda2bee8eccc91dea9558c0b4855b5bf95",
+ "globalAliases": [
+ "alex"
+ ],
+ "localAliases": [],
+ "permissions": {
+ "read": true,
+ "write": true,
+ "owner": true
+ }
+ }
+ ]
+}
+```
+
+#### DeleteKey `DELETE /v0/key?id=<acces key id>`
+
+Deletes an API access key.
+
+#### UpdateKey `POST /v0/key?id=<acces key id>`
+
+Updates information about the specified API access key.
+
+Request body format:
+
+```json
+{
+ "name": "NameOfMyKey",
+ "allow": {
+ "createBucket": true,
+ },
+ "deny": {}
+}
+```
+
+All fields (`name`, `allow` and `deny`) are optionnal.
+If they are present, the corresponding modifications are applied to the key, otherwise nothing is changed.
+The possible flags in `allow` and `deny` are: `createBucket`.
+
+
+### Bucket operations
+
+#### ListBuckets `GET /v0/bucket`
+
+Returns all storage buckets in the cluster.
+
+Example response:
+
+```json
+[
+ {
+ "id": "70dc3bed7fe83a75e46b66e7ddef7d56e65f3c02f9f80b6749fb97eccb5e1033",
+ "globalAliases": [
+ "test2"
+ ],
+ "localAliases": []
+ },
+ {
+ "id": "96470e0df00ec28807138daf01915cfda2bee8eccc91dea9558c0b4855b5bf95",
+ "globalAliases": [
+ "alex"
+ ],
+ "localAliases": []
+ },
+ {
+ "id": "d7452a935e663fc1914f3a5515163a6d3724010ce8dfd9e4743ca8be5974f995",
+ "globalAliases": [
+ "test3"
+ ],
+ "localAliases": []
+ },
+ {
+ "id": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
+ "globalAliases": [],
+ "localAliases": [
+ {
+ "accessKeyId": "GK31c2f218a2e44f485b94239e",
+ "alias": "test"
+ }
+ ]
+ }
+]
+```
+
+#### GetBucketInfo `GET /v0/bucket?id=<bucket id>`
+#### GetBucketInfo `GET /v0/bucket?globalAlias=<alias>`
+
+Returns information about the requested storage bucket.
+
+If `id` is set, the bucket is looked up using its exact identifier.
+If `globalAlias` is set, the bucket is looked up using its global alias.
+(both are fast)
+
+Example response:
+
+```json
+{
+ "id": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
+ "globalAliases": [
+ "alex"
+ ],
+ "keys": [
+ {
+ "accessKeyId": "GK31c2f218a2e44f485b94239e",
+ "name": "alex",
+ "permissions": {
+ "read": true,
+ "write": true,
+ "owner": true
+ },
+ "bucketLocalAliases": [
+ "test"
+ ]
+ }
+ ]
+}
+```
+
+#### CreateBucket `POST /v0/bucket`
+
+Creates a new storage bucket.
+
+Request body format:
+
+```json
+{
+ "globalAlias": "NameOfMyBucket"
+}
+```
+
+OR
+
+```json
+{
+ "localAlias": {
+ "accessKeyId": "GK31c2f218a2e44f485b94239e",
+ "alias": "NameOfMyBucket",
+ "allow": {
+ "read": true,
+ "write": true,
+ "owner": false
+ }
+ }
+}
+```
+
+OR
+
+```json
+{}
+```
+
+Creates a new bucket, either with a global alias, a local one,
+or no alias at all.
+
+Technically, you can also specify both `globalAlias` and `localAlias` and that would create
+two aliases, but I don't see why you would want to do that.
+
+#### DeleteBucket `DELETE /v0/bucket?id=<bucket id>`
+
+Deletes a storage bucket. A bucket cannot be deleted if it is not empty.
+
+Warning: this will delete all aliases associated with the bucket!
+
+#### PutBucketWebsite `PUT /v0/bucket/website?id=<bucket id>`
+
+Sets the website configuration for a bucket (this also enables website access for this bucket).
+
+Request body format:
+
+```json
+{
+ "indexDocument": "index.html",
+ "errorDocument": "404.html"
+}
+```
+
+The field `errorDocument` is optional, if no error document is set a generic error message is displayed when errors happen.
+
+
+#### DeleteBucketWebsite `DELETE /v0/bucket/website?id=<bucket id>`
+
+Deletes the website configuration for a bucket (disables website access for this bucket).
+
+
+### Operations on permissions for keys on buckets
+
+#### BucketAllowKey `POST /v0/bucket/allow`
+
+Allows a key to do read/write/owner operations on a bucket.
+
+Request body format:
+
+```json
+{
+ "bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
+ "accessKeyId": "GK31c2f218a2e44f485b94239e",
+ "permissions": {
+ "read": true,
+ "write": true,
+ "owner": true
+ },
+}
+```
+
+Flags in `permissions` which have the value `true` will be activated.
+Other flags will remain unchanged.
+
+#### BucketDenyKey `POST /v0/bucket/deny`
+
+Denies a key from doing read/write/owner operations on a bucket.
+
+Request body format:
+
+```json
+{
+ "bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b",
+ "accessKeyId": "GK31c2f218a2e44f485b94239e",
+ "permissions": {
+ "read": false,
+ "write": false,
+ "owner": true
+ },
+}
+```
+
+Flags in `permissions` which have the value `true` will be deactivated.
+Other flags will remain unchanged.
+
+
+### Operations on bucket aliases
+
+#### GlobalAliasBucket `PUT /v0/bucket/alias/global?id=<bucket id>&alias=<global alias>`
+
+Empty body. Creates a global alias for a bucket.
+
+#### GlobalUnaliasBucket `DELETE /v0/bucket/alias/global?id=<bucket id>&alias=<global alias>`
+
+Removes a global alias for a bucket.
+
+#### LocalAliasBucket `PUT /v0/bucket/alias/local?id=<bucket id>&accessKeyId=<access key ID>&alias=<local alias>`
+
+Empty body. Creates a local alias for a bucket in the namespace of a specific access key.
+
+#### LocalUnaliasBucket `DELETE /v0/bucket/alias/local?id=<bucket id>&accessKeyId<access key ID>&alias=<local alias>`
+
+Removes a local alias for a bucket in the namespace of a specific access key.
+