0.34.6_repo_cleanup
This commit is contained in:
1 parent
ce1a90397c
commit
133d2f0f91
273 files changed
+5969
-6926
No files matched your search
+80
-25
@@ -15,37 +15,68 @@ pub struct ApiTokenMetadata {
|
||||
pub last_used_at: Option<i64>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct ApiTokenGrant { pub id: String, pub permission: String }
|
||||
pub struct ApiTokenGrant {
|
||||
pub id: String,
|
||||
pub permission: String,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub async fn api_tokens(&self) -> Result<Vec<ApiTokenMetadata>> {
|
||||
let rows = sqlx::query("SELECT id,permission,comment,allowed_cidrs,created_at,last_used_at FROM administration_api_tokens ORDER BY created_at DESC,id")
|
||||
.fetch_all(&self.pool).await?;
|
||||
rows.into_iter().map(|row| {
|
||||
let cidrs: String = row.try_get("allowed_cidrs")?;
|
||||
Ok(ApiTokenMetadata { id: row.try_get("id")?, permission: row.try_get("permission")?,
|
||||
comment: row.try_get("comment")?, allowed_cidrs: serde_json::from_str(&cidrs)?,
|
||||
created_at: row.try_get("created_at")?, last_used_at: row.try_get("last_used_at")? })
|
||||
}).collect()
|
||||
rows.into_iter()
|
||||
.map(|row| {
|
||||
let cidrs: String = row.try_get("allowed_cidrs")?;
|
||||
Ok(ApiTokenMetadata {
|
||||
id: row.try_get("id")?,
|
||||
permission: row.try_get("permission")?,
|
||||
comment: row.try_get("comment")?,
|
||||
allowed_cidrs: serde_json::from_str(&cidrs)?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
last_used_at: row.try_get("last_used_at")?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
pub async fn create_api_token(&self, id: &str, hash: &str, permission: &str,
|
||||
comment: &str, cidrs: &[String], actor: &str) -> Result<bool> {
|
||||
pub async fn create_api_token(
|
||||
&self,
|
||||
id: &str,
|
||||
hash: &str,
|
||||
permission: &str,
|
||||
comment: &str,
|
||||
cidrs: &[String],
|
||||
actor: &str,
|
||||
) -> Result<bool> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM administration_api_tokens").fetch_one(&mut *tx).await?;
|
||||
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM administration_api_tokens")
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
ensure!(count < API_TOKEN_CAPACITY, "API token limit reached");
|
||||
let result = sqlx::query("INSERT OR IGNORE INTO administration_api_tokens(id,token_hash,permission,comment,allowed_cidrs,created_at) VALUES(?,?,?,?,?,?)")
|
||||
.bind(id).bind(hash).bind(permission).bind(comment).bind(serde_json::to_string(cidrs)?)
|
||||
.bind(crate::auth::now()).execute(&mut *tx).await?;
|
||||
if result.rows_affected() != 1 { return Ok(false); }
|
||||
audit_tx(&mut tx, actor, "api_token.create", &format!("id={id} permission={permission}")).await?;
|
||||
if result.rows_affected() != 1 {
|
||||
return Ok(false);
|
||||
}
|
||||
audit_tx(
|
||||
&mut tx,
|
||||
actor,
|
||||
"api_token.create",
|
||||
&format!("id={id} permission={permission}"),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(true)
|
||||
}
|
||||
pub async fn revoke_api_token(&self, id: &str, actor: &str) -> Result<bool> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let result = sqlx::query("DELETE FROM administration_api_tokens WHERE id = ?")
|
||||
.bind(id).execute(&mut *tx).await?;
|
||||
if result.rows_affected() == 0 { return Ok(false); }
|
||||
.bind(id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
if result.rows_affected() == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
audit_tx(&mut tx, actor, "api_token.revoke", &format!("id={id}")).await?;
|
||||
tx.commit().await?;
|
||||
Ok(true)
|
||||
@@ -54,39 +85,63 @@ impl Store {
|
||||
let Some(row) = sqlx::query("SELECT id,permission,allowed_cidrs FROM administration_api_tokens WHERE token_hash = ?")
|
||||
.bind(hash).fetch_optional(&self.pool).await? else { return Ok(None); };
|
||||
let cidrs: String = row.try_get("allowed_cidrs")?;
|
||||
let allowed: Vec<String> = serde_json::from_str(&cidrs).context("invalid stored CIDR list")?;
|
||||
if !allowed.iter().filter_map(|s| s.parse::<ipnet::IpNet>().ok()).any(|net| net.contains(&ip)) {
|
||||
let allowed: Vec<String> =
|
||||
serde_json::from_str(&cidrs).context("invalid stored CIDR list")?;
|
||||
if !allowed
|
||||
.iter()
|
||||
.filter_map(|s| s.parse::<ipnet::IpNet>().ok())
|
||||
.any(|net| net.contains(&ip))
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
let id: String = row.try_get("id")?;
|
||||
let permission: String = row.try_get("permission")?;
|
||||
sqlx::query("UPDATE administration_api_tokens SET last_used_at = ? WHERE id = ?")
|
||||
.bind(crate::auth::now()).bind(&id).execute(&self.pool).await?;
|
||||
.bind(crate::auth::now())
|
||||
.bind(&id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(Some(ApiTokenGrant { id, permission }))
|
||||
}
|
||||
pub async fn prometheus_token_hash(&self) -> Result<Option<String>> {
|
||||
Ok(sqlx::query_scalar("SELECT token_hash FROM prometheus_credential WHERE id = 1")
|
||||
.fetch_optional(&self.pool).await?)
|
||||
Ok(
|
||||
sqlx::query_scalar("SELECT token_hash FROM prometheus_credential WHERE id = 1")
|
||||
.fetch_optional(&self.pool)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
pub async fn prometheus_token_status(&self) -> Result<Option<i64>> {
|
||||
Ok(sqlx::query_scalar("SELECT created_at FROM prometheus_credential WHERE id = 1 AND token_hash != 'revoked'")
|
||||
.fetch_optional(&self.pool).await?)
|
||||
Ok(sqlx::query_scalar(
|
||||
"SELECT created_at FROM prometheus_credential WHERE id = 1 AND token_hash != 'revoked'",
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?)
|
||||
}
|
||||
pub async fn rotate_prometheus_token(&self, hash: &str, actor: &str) -> Result<()> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
sqlx::query("INSERT INTO prometheus_credential(id,token_hash,created_at) VALUES(1,?,?) ON CONFLICT(id) DO UPDATE SET token_hash=excluded.token_hash,created_at=excluded.created_at")
|
||||
.bind(hash).bind(crate::auth::now()).execute(&mut *tx).await?;
|
||||
audit_tx(&mut tx, actor, "prometheus_token.rotate", "exporter token rotated").await?;
|
||||
audit_tx(
|
||||
&mut tx,
|
||||
actor,
|
||||
"prometheus_token.rotate",
|
||||
"exporter token rotated",
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn revoke_prometheus_token(&self, actor: &str) -> Result<()> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
// Keep a disabled marker so an old environment token does not silently
|
||||
// become valid again after the operator clicks Revoke in the panel.
|
||||
sqlx::query("INSERT INTO prometheus_credential(id,token_hash,created_at) VALUES(1,'revoked',?) ON CONFLICT(id) DO UPDATE SET token_hash='revoked',created_at=excluded.created_at")
|
||||
.bind(crate::auth::now()).execute(&mut *tx).await?;
|
||||
audit_tx(&mut tx, actor, "prometheus_token.revoke", "exporter token revoked").await?;
|
||||
audit_tx(
|
||||
&mut tx,
|
||||
actor,
|
||||
"prometheus_token.revoke",
|
||||
"exporter token revoked",
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+27
-14
@@ -5,7 +5,10 @@ use super::{
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use sqlx::Row;
|
||||
|
||||
fn normalized_certificate_api_ids(comment: &str, certificate_ids: &[String]) -> Result<Vec<String>> {
|
||||
fn normalized_certificate_api_ids(
|
||||
comment: &str,
|
||||
certificate_ids: &[String],
|
||||
) -> Result<Vec<String>> {
|
||||
ensure!(
|
||||
comment.len() <= 256 && !comment.chars().any(char::is_control),
|
||||
"invalid certificate API comment"
|
||||
@@ -18,7 +21,9 @@ fn normalized_certificate_api_ids(comment: &str, certificate_ids: &[String]) ->
|
||||
normalized.sort();
|
||||
normalized.dedup();
|
||||
ensure!(
|
||||
normalized.iter().all(|value| value != "*" && crate::config::valid_id(value)),
|
||||
normalized
|
||||
.iter()
|
||||
.all(|value| value != "*" && crate::config::valid_id(value)),
|
||||
"invalid certificate API certificate id"
|
||||
);
|
||||
ensure!(
|
||||
@@ -36,7 +41,8 @@ fn credential_from_row(row: sqlx::sqlite::SqliteRow) -> Result<CertificateApiCre
|
||||
created_at: row.try_get("created_at")?,
|
||||
last_used_at: row.try_get("last_used_at")?,
|
||||
allow_deploy: row.try_get::<i64, _>("allow_deploy")? != 0,
|
||||
certificate_ids: serde_json::from_str(&raw_ids).context("invalid certificate API grant ids")?,
|
||||
certificate_ids: serde_json::from_str(&raw_ids)
|
||||
.context("invalid certificate API grant ids")?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,7 +141,6 @@ impl Store {
|
||||
})
|
||||
}
|
||||
|
||||
// Metadata access never selects token_hash, including for a single credential.
|
||||
pub async fn certificate_api_credential(
|
||||
&self,
|
||||
key_id: &str,
|
||||
@@ -154,7 +159,6 @@ impl Store {
|
||||
row.map(credential_from_row).transpose()
|
||||
}
|
||||
|
||||
// Do not touch token_hash, created_at, or last_used_at while editing settings.
|
||||
pub async fn edit_certificate_api_credential(
|
||||
&self,
|
||||
key_id: &str,
|
||||
@@ -170,11 +174,12 @@ impl Store {
|
||||
let normalized_ids = normalized_certificate_api_ids(comment, certificate_ids)?;
|
||||
let ids_json = serde_json::to_string(&normalized_ids)?;
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let changed = sqlx::query("UPDATE certificate_api_credentials SET comment = ? WHERE key_id = ?")
|
||||
.bind(comment)
|
||||
.bind(key_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
let changed =
|
||||
sqlx::query("UPDATE certificate_api_credentials SET comment = ? WHERE key_id = ?")
|
||||
.bind(comment)
|
||||
.bind(key_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
if changed.rows_affected() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -193,7 +198,11 @@ impl Store {
|
||||
"certificate_api.edit",
|
||||
&format!(
|
||||
"key_id={key_id}; scope={}; certificate_ids={}",
|
||||
if allow_deploy { "deploy+reload" } else { "reload" },
|
||||
if allow_deploy {
|
||||
"deploy+reload"
|
||||
} else {
|
||||
"reload"
|
||||
},
|
||||
normalized_ids.join(",")
|
||||
),
|
||||
)
|
||||
@@ -202,7 +211,6 @@ impl Store {
|
||||
self.certificate_api_credential(key_id).await
|
||||
}
|
||||
|
||||
// Hash-only rotation preserves comment/grants and invalidates the old token at commit.
|
||||
pub async fn regenerate_certificate_api_token(
|
||||
&self,
|
||||
key_id: &str,
|
||||
@@ -231,8 +239,13 @@ impl Store {
|
||||
if changed.rows_affected() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
audit_tx(&mut tx, actor, "certificate_api.regenerate", &format!("key_id={key_id}"))
|
||||
.await?;
|
||||
audit_tx(
|
||||
&mut tx,
|
||||
actor,
|
||||
"certificate_api.regenerate",
|
||||
&format!("key_id={key_id}"),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
self.certificate_api_credential(key_id).await
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//! SQLite persistence for user-named Overview snapshots (control plane only).
|
||||
use super::Store;
|
||||
use anyhow::{ensure, Result};
|
||||
use sqlx::Row;
|
||||
@@ -31,8 +30,6 @@ impl Store {
|
||||
document.len() >= 2 && document.len() <= 8192,
|
||||
"invalid dashboard preset size"
|
||||
);
|
||||
// Limit to 16 user-named presets. The cap and upsert are checked in one
|
||||
// SQLite statement, including under concurrent requests.
|
||||
let result = sqlx::query(
|
||||
"INSERT INTO dashboard_presets(administrator_id, name, document, updated_at) \
|
||||
SELECT a.id, ?, ?, ? FROM administrator a WHERE a.id = 1 AND a.username = ? \
|
||||
@@ -65,7 +62,6 @@ impl Store {
|
||||
old: &str,
|
||||
new: &str,
|
||||
) -> Result<bool> {
|
||||
// UPDATE OR IGNORE ensures an existing target name is never overwritten.
|
||||
let result = sqlx::query(
|
||||
"UPDATE OR IGNORE dashboard_presets SET name = ?, updated_at = ? \
|
||||
WHERE administrator_id = (SELECT id FROM administrator WHERE id = 1 AND username = ?) \
|
||||
|
||||
Reference in new issue
Block a user