admin docs: reformatting, key admin: add check

This commit is contained in:
Alex Auvolat 2023-06-14 17:13:41 +02:00
parent a83a092c03
commit 8ef42c9609
5 changed files with 69 additions and 64 deletions

View File

@ -363,6 +363,7 @@ existing layout in the cluster.
This returns the new cluster layout with all changes reverted, This returns the new cluster layout with all changes reverted,
as returned by GetClusterLayout. as returned by GetClusterLayout.
### Access key operations ### Access key operations
#### ListKeys `GET /v1/key` #### ListKeys `GET /v1/key`
@ -384,37 +385,6 @@ Example response:
] ]
``` ```
#### CreateKey `POST /v1/key`
Creates a new API access key.
Request body format:
```json
{
"name": "NameOfMyKey"
}
```
This returns the key info, including the created secret key,
in the same format as the result of GetKeyInfo.
#### ImportKey `POST /v1/key/import`
Imports an existing API key.
Request body format:
```json
{
"accessKeyId": "GK31c2f218a2e44f485b94239e",
"secretAccessKey": "b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835",
"name": "NameOfMyKey"
}
```
This returns the key info in the same format as the result of GetKeyInfo.
#### GetKeyInfo `GET /v1/key?id=<acces key id>` #### GetKeyInfo `GET /v1/key?id=<acces key id>`
#### GetKeyInfo `GET /v1/key?search=<pattern>` #### GetKeyInfo `GET /v1/key?search=<pattern>`
@ -490,9 +460,38 @@ Example response:
} }
``` ```
#### DeleteKey `DELETE /v1/key?id=<acces key id>` #### CreateKey `POST /v1/key`
Deletes an API access key. Creates a new API access key.
Request body format:
```json
{
"name": "NameOfMyKey"
}
```
This returns the key info, including the created secret key,
in the same format as the result of GetKeyInfo.
#### ImportKey `POST /v1/key/import`
Imports an existing API key.
This will check that the imported key is in the valid format, i.e.
is a key that could have been generated by Garage.
Request body format:
```json
{
"accessKeyId": "GK31c2f218a2e44f485b94239e",
"secretAccessKey": "b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835",
"name": "NameOfMyKey"
}
```
This returns the key info in the same format as the result of GetKeyInfo.
#### UpdateKey `POST /v1/key?id=<acces key id>` #### UpdateKey `POST /v1/key?id=<acces key id>`
@ -516,6 +515,11 @@ The possible flags in `allow` and `deny` are: `createBucket`.
This returns the key info in the same format as the result of GetKeyInfo. This returns the key info in the same format as the result of GetKeyInfo.
#### DeleteKey `DELETE /v1/key?id=<acces key id>`
Deletes an API access key.
### Bucket operations ### Bucket operations
#### ListBuckets `GET /v1/bucket` #### ListBuckets `GET /v1/bucket`
@ -644,12 +648,6 @@ or no alias at all.
Technically, you can also specify both `globalAlias` and `localAlias` and that would create 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. two aliases, but I don't see why you would want to do that.
#### DeleteBucket `DELETE /v1/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!
#### UpdateBucket `PUT /v1/bucket?id=<bucket id>` #### UpdateBucket `PUT /v1/bucket?id=<bucket id>`
Updates configuration of the given bucket. Updates configuration of the given bucket.
@ -682,6 +680,13 @@ In `quotas`: new values of `maxSize` and `maxObjects` must both be specified, or
to remove the quotas. An absent value will be considered the same as a `null`. It is not possible to remove the quotas. An absent value will be considered the same as a `null`. It is not possible
to change only one of the two quotas. to change only one of the two quotas.
#### DeleteBucket `DELETE /v1/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!
### Operations on permissions for keys on buckets ### Operations on permissions for keys on buckets
#### BucketAllowKey `POST /v1/bucket/allow` #### BucketAllowKey `POST /v1/bucket/allow`

View File

@ -1,7 +1,7 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use garage_util::crdt::*; use garage_util::crdt::*;
@ -161,8 +161,8 @@ struct GetClusterStatusResponse {
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct ApplyClusterLayoutResponse { struct ApplyClusterLayoutResponse {
message: Vec<String>, message: Vec<String>,
layout: GetClusterLayoutResponse, layout: GetClusterLayoutResponse,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -238,7 +238,7 @@ pub async fn handle_update_cluster_layout(
garage.system.update_cluster_layout(&layout).await?; garage.system.update_cluster_layout(&layout).await?;
let res = format_cluster_layout(&layout); let res = format_cluster_layout(&layout);
Ok(json_ok_response(&res)?) Ok(json_ok_response(&res)?)
} }
pub async fn handle_apply_cluster_layout( pub async fn handle_apply_cluster_layout(
@ -253,10 +253,10 @@ pub async fn handle_apply_cluster_layout(
garage.system.update_cluster_layout(&layout).await?; garage.system.update_cluster_layout(&layout).await?;
let res = ApplyClusterLayoutResponse { let res = ApplyClusterLayoutResponse {
message: msg, message: msg,
layout: format_cluster_layout(&layout), layout: format_cluster_layout(&layout),
}; };
Ok(json_ok_response(&res)?) Ok(json_ok_response(&res)?)
} }
pub async fn handle_revert_cluster_layout( pub async fn handle_revert_cluster_layout(
@ -270,7 +270,7 @@ pub async fn handle_revert_cluster_layout(
garage.system.update_cluster_layout(&layout).await?; garage.system.update_cluster_layout(&layout).await?;
let res = format_cluster_layout(&layout); let res = format_cluster_layout(&layout);
Ok(json_ok_response(&res)?) Ok(json_ok_response(&res)?)
} }
// ---- // ----

View File

@ -93,7 +93,8 @@ pub async fn handle_import_key(
&req.access_key_id, &req.access_key_id,
&req.secret_access_key, &req.secret_access_key,
req.name.as_deref().unwrap_or("Imported key"), req.name.as_deref().unwrap_or("Imported key"),
); )
.ok_or_bad_request("Invalid key format")?;
garage.key_table.insert(&imported_key).await?; garage.key_table.insert(&imported_key).await?;
key_info_results(garage, imported_key, false).await key_info_results(garage, imported_key, false).await

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use garage_table::*; use garage_table::*;
use garage_model::helper::error::Error; use garage_model::helper::error::*;
use garage_model::key_table::*; use garage_model::key_table::*;
use crate::cli::*; use crate::cli::*;
@ -127,22 +127,13 @@ impl AdminRpcHandler {
return Err(Error::BadRequest("This command is intended to re-import keys that were previously generated by Garage. If you want to create a new key, use `garage key new` instead. Add the --yes flag if you really want to re-import a key.".to_string())); return Err(Error::BadRequest("This command is intended to re-import keys that were previously generated by Garage. If you want to create a new key, use `garage key new` instead. Add the --yes flag if you really want to re-import a key.".to_string()));
} }
if query.key_id.len() != 26
|| &query.key_id[..2] != "GK"
|| hex::decode(&query.key_id[2..]).is_err()
{
return Err(Error::BadRequest(format!("The specified key ID is not a valid Garage key ID (starts with `GK`, followed by 12 hex-encoded bytes)")));
}
if query.secret_key.len() != 64 || hex::decode(&query.secret_key).is_err() {
return Err(Error::BadRequest(format!("The specified secret key is not a valid Garage secret key (composed of 32 hex-encoded bytes)")));
}
let prev_key = self.garage.key_table.get(&EmptyKey, &query.key_id).await?; let prev_key = self.garage.key_table.get(&EmptyKey, &query.key_id).await?;
if prev_key.is_some() { if prev_key.is_some() {
return Err(Error::BadRequest(format!("Key {} already exists in data store. Even if it is deleted, we can't let you create a new key with the same ID. Sorry.", query.key_id))); return Err(Error::BadRequest(format!("Key {} already exists in data store. Even if it is deleted, we can't let you create a new key with the same ID. Sorry.", query.key_id)));
} }
let imported_key = Key::import(&query.key_id, &query.secret_key, &query.name); let imported_key = Key::import(&query.key_id, &query.secret_key, &query.name)
.ok_or_bad_request("Invalid key format")?;
self.garage.key_table.insert(&imported_key).await?; self.garage.key_table.insert(&imported_key).await?;
self.key_info_result(imported_key).await self.key_info_result(imported_key).await

View File

@ -149,11 +149,19 @@ impl Key {
} }
/// Import a key from it's parts /// Import a key from it's parts
pub fn import(key_id: &str, secret_key: &str, name: &str) -> Self { pub fn import(key_id: &str, secret_key: &str, name: &str) -> Result<Self, &'static str> {
Self { if key_id.len() != 26 || &key_id[..2] != "GK" || hex::decode(&key_id[2..]).is_err() {
return Err("The specified key ID is not a valid Garage key ID (starts with `GK`, followed by 12 hex-encoded bytes)");
}
if secret_key.len() != 64 || hex::decode(&secret_key).is_err() {
return Err("The specified secret key is not a valid Garage secret key (composed of 32 hex-encoded bytes)");
}
Ok(Self {
key_id: key_id.to_string(), key_id: key_id.to_string(),
state: crdt::Deletable::present(KeyParams::new(secret_key, name)), state: crdt::Deletable::present(KeyParams::new(secret_key, name)),
} })
} }
/// Create a new Key which can me merged to mark an existing key deleted /// Create a new Key which can me merged to mark an existing key deleted