mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-08-31 01:09:24 +00:00
@@ -869,6 +869,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/IntrospectAdminToken": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Admin API token"
|
||||||
|
],
|
||||||
|
"description": "\nReturn information about the calling admin API token.\n ",
|
||||||
|
"operationId": "IntrospectAdminToken",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "admin_token",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Information about the admin token",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/IntrospectAdminTokenResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/GetNodeInfo": {
|
"/v2/GetNodeInfo": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -2644,6 +2678,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"IntrospectAdminTokenResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"expired",
|
||||||
|
"scope"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"created": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Creation date"
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Expiration time and date, formatted according to RFC 3339"
|
||||||
|
},
|
||||||
|
"expired": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether this admin token is expired already"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"description": "Identifier of the admin token (which is also a prefix of the full bearer token)"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the admin API token"
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Scope of the admin API token, a list of admin endpoint names (such as\n`GetClusterStatus`, etc), or the special value `*` to allow all\nadmin endpoints"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ImportKeyRequest": {
|
"ImportKeyRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -175,6 +175,79 @@ impl RequestHandler for DeleteAdminTokenRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RequestHandler for IntrospectAdminTokenRequest {
|
||||||
|
type Response = IntrospectAdminTokenResponse;
|
||||||
|
|
||||||
|
async fn handle(
|
||||||
|
self,
|
||||||
|
garage: &Arc<Garage>,
|
||||||
|
_admin: &Admin,
|
||||||
|
) -> Result<IntrospectAdminTokenResponse, Error> {
|
||||||
|
let now = now_msec();
|
||||||
|
|
||||||
|
if garage
|
||||||
|
.config
|
||||||
|
.admin
|
||||||
|
.metrics_token
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|s| s == &self.admin_token)
|
||||||
|
{
|
||||||
|
return Ok(IntrospectAdminTokenResponse {
|
||||||
|
id: None,
|
||||||
|
created: None,
|
||||||
|
name: "metrics_token (from daemon configuration)".into(),
|
||||||
|
expiration: None,
|
||||||
|
expired: false,
|
||||||
|
scope: vec!["Metrics".into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if garage
|
||||||
|
.config
|
||||||
|
.admin
|
||||||
|
.admin_token
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|s| s == &self.admin_token)
|
||||||
|
{
|
||||||
|
return Ok(IntrospectAdminTokenResponse {
|
||||||
|
id: None,
|
||||||
|
created: None,
|
||||||
|
name: "admin_token (from daemon configuration)".into(),
|
||||||
|
expiration: None,
|
||||||
|
expired: false,
|
||||||
|
scope: vec!["*".into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let (prefix, _) = self.admin_token.split_once('.').unwrap();
|
||||||
|
|
||||||
|
let candidates = garage
|
||||||
|
.admin_token_table
|
||||||
|
.get_range(
|
||||||
|
&EmptyKey,
|
||||||
|
None,
|
||||||
|
Some(KeyFilter::MatchesAndNotDeleted(
|
||||||
|
prefix.clone().parse().unwrap(),
|
||||||
|
)),
|
||||||
|
10,
|
||||||
|
EnumerationOrder::Forward,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if candidates.len() != 1 {
|
||||||
|
return Err(Error::bad_request(format!(
|
||||||
|
"{} matching admin tokens",
|
||||||
|
candidates.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(my_admin_token_info_results(
|
||||||
|
&candidates.into_iter().next().unwrap(),
|
||||||
|
now,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- helpers ----
|
// ---- helpers ----
|
||||||
|
|
||||||
fn admin_token_info_results(token: &AdminApiToken, now: u64) -> GetAdminTokenInfoResponse {
|
fn admin_token_info_results(token: &AdminApiToken, now: u64) -> GetAdminTokenInfoResponse {
|
||||||
@@ -233,3 +306,21 @@ fn apply_token_updates(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn my_admin_token_info_results(token: &AdminApiToken, now: u64) -> IntrospectAdminTokenResponse {
|
||||||
|
let params = token.params().unwrap();
|
||||||
|
|
||||||
|
IntrospectAdminTokenResponse {
|
||||||
|
id: Some(token.prefix.clone()),
|
||||||
|
created: Some(
|
||||||
|
DateTime::from_timestamp_millis(params.created as i64)
|
||||||
|
.expect("invalid timestamp stored in db"),
|
||||||
|
),
|
||||||
|
name: params.name.get().to_string(),
|
||||||
|
expiration: params.expiration.get().map(|x| {
|
||||||
|
DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")
|
||||||
|
}),
|
||||||
|
expired: params.is_expired(now),
|
||||||
|
scope: params.scope.get().0.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ admin_endpoints![
|
|||||||
CreateAdminToken,
|
CreateAdminToken,
|
||||||
UpdateAdminToken,
|
UpdateAdminToken,
|
||||||
DeleteAdminToken,
|
DeleteAdminToken,
|
||||||
|
IntrospectAdminToken,
|
||||||
|
|
||||||
// Layout operations
|
// Layout operations
|
||||||
GetClusterLayout,
|
GetClusterLayout,
|
||||||
@@ -391,6 +392,29 @@ pub struct DeleteAdminTokenRequest {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct DeleteAdminTokenResponse;
|
pub struct DeleteAdminTokenResponse;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, IntoParams)]
|
||||||
|
pub struct IntrospectAdminTokenRequest {
|
||||||
|
pub admin_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct IntrospectAdminTokenResponse {
|
||||||
|
/// Identifier of the admin token (which is also a prefix of the full bearer token)
|
||||||
|
pub id: Option<String>,
|
||||||
|
/// Creation date
|
||||||
|
pub created: Option<DateTime<Utc>>,
|
||||||
|
/// Name of the admin API token
|
||||||
|
pub name: String,
|
||||||
|
/// Expiration time and date, formatted according to RFC 3339
|
||||||
|
pub expiration: Option<DateTime<Utc>>,
|
||||||
|
/// Whether this admin token is expired already
|
||||||
|
pub expired: bool,
|
||||||
|
/// Scope of the admin API token, a list of admin endpoint names (such as
|
||||||
|
/// `GetClusterStatus`, etc), or the special value `*` to allow all
|
||||||
|
/// admin endpoints
|
||||||
|
pub scope: Vec<String>,
|
||||||
|
}
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// Layout operations
|
// Layout operations
|
||||||
// **********************************************
|
// **********************************************
|
||||||
|
|||||||
@@ -191,6 +191,20 @@ fn UpdateAdminToken() -> () {}
|
|||||||
)]
|
)]
|
||||||
fn DeleteAdminToken() -> () {}
|
fn DeleteAdminToken() -> () {}
|
||||||
|
|
||||||
|
#[utoipa::path(get,
|
||||||
|
path = "/v2/IntrospectAdminToken",
|
||||||
|
tag = "Admin API token",
|
||||||
|
description = "
|
||||||
|
Return information about the calling admin API token.
|
||||||
|
",
|
||||||
|
params(IntrospectAdminTokenRequest),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Information about the admin token", body = IntrospectAdminTokenResponse),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
fn IntrospectAdminToken() -> () {}
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// Layout operations
|
// Layout operations
|
||||||
// **********************************************
|
// **********************************************
|
||||||
@@ -872,6 +886,7 @@ impl Modify for SecurityAddon {
|
|||||||
CreateAdminToken,
|
CreateAdminToken,
|
||||||
UpdateAdminToken,
|
UpdateAdminToken,
|
||||||
DeleteAdminToken,
|
DeleteAdminToken,
|
||||||
|
IntrospectAdminToken,
|
||||||
// Layout operations
|
// Layout operations
|
||||||
GetClusterLayout,
|
GetClusterLayout,
|
||||||
GetClusterLayoutHistory,
|
GetClusterLayoutHistory,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ impl AdminApiRequest {
|
|||||||
POST CreateAdminToken (body),
|
POST CreateAdminToken (body),
|
||||||
POST UpdateAdminToken (body_field, query::id),
|
POST UpdateAdminToken (body_field, query::id),
|
||||||
POST DeleteAdminToken (query::id),
|
POST DeleteAdminToken (query::id),
|
||||||
|
GET IntrospectAdminToken (admin_token),
|
||||||
// Layout endpoints
|
// Layout endpoints
|
||||||
GET GetClusterLayout (),
|
GET GetClusterLayout (),
|
||||||
GET GetClusterLayoutHistory (),
|
GET GetClusterLayoutHistory (),
|
||||||
|
|||||||
@@ -83,6 +83,21 @@ macro_rules! router_match {
|
|||||||
parse_json_body::< [<$api Request>], _, Error>($req).await?
|
parse_json_body::< [<$api Request>], _, Error>($req).await?
|
||||||
})
|
})
|
||||||
}};
|
}};
|
||||||
|
(@@gen_parse_request $api:ident, (admin_token), $query: expr, $req:expr) => {{
|
||||||
|
paste!({
|
||||||
|
let auth_header = $req.headers()
|
||||||
|
.get(hyper::header::AUTHORIZATION)
|
||||||
|
.ok_or_else(|| Error::bad_request("Missing Authorization header"))?
|
||||||
|
.to_str()
|
||||||
|
.map_err(|_| Error::bad_request("Invalid Authorization header"))?;
|
||||||
|
|
||||||
|
let admin_token = auth_header.strip_prefix("Bearer ")
|
||||||
|
.ok_or_else(|| Error::bad_request("Authorization header must be Bearer token"))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
[< $api Request >] { admin_token }
|
||||||
|
})
|
||||||
|
}};
|
||||||
(@@gen_parse_request $api:ident, (body_field, $($conv:ident $(($conv_arg:expr))? :: $param:ident),*), $query: expr, $req:expr)
|
(@@gen_parse_request $api:ident, (body_field, $($conv:ident $(($conv_arg:expr))? :: $param:ident),*), $query: expr, $req:expr)
|
||||||
=>
|
=>
|
||||||
{{
|
{{
|
||||||
|
|||||||
Reference in New Issue
Block a user