0.34.6_repo_cleanup

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 09:31:15 +02:00
1 parent ce1a90397c
commit 133d2f0f91
273 files changed
+5969 -6926

No files matched your search

+80 -25
View File
@@ -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
View File
@@ -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
}
-4
View File
@@ -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 = ?) \