fix: CNPG readiness, DKIM SPKI format, kv_patch, container name

- Check CNPG Cluster CRD status.phase instead of pod Running phase
- DKIM public key: use SPKI format (BEGIN PUBLIC KEY) matching Python
- Use kv_patch instead of kv_put for dirty paths (preserves external fields)
- Vault KV only written when password is newly generated
- Gitea exec passes container name Some("gitea")
- Fix openbao comment (400 not 409)
This commit is contained in:
2026-03-20 13:29:59 +00:00
parent 6ec0666aa1
commit 24e98b4e7d
2 changed files with 44 additions and 27 deletions

View File

@@ -158,7 +158,7 @@ impl BaoClient {
// ── Secrets engine management ─────────────────────────────────────── // ── Secrets engine management ───────────────────────────────────────
/// Enable a secrets engine at the given path. /// Enable a secrets engine at the given path.
/// Returns Ok(()) even if already enabled (409 is tolerated). /// Returns Ok(()) even if already enabled (400 is tolerated).
pub async fn enable_secrets_engine(&self, path: &str, engine_type: &str) -> Result<()> { pub async fn enable_secrets_engine(&self, path: &str, engine_type: &str) -> Result<()> {
#[derive(Serialize)] #[derive(Serialize)]
struct EnableRequest<'a> { struct EnableRequest<'a> {

View File

@@ -6,10 +6,9 @@
use crate::error::{Result, ResultExt, SunbeamError}; use crate::error::{Result, ResultExt, SunbeamError};
use k8s_openapi::api::core::v1::Pod; use k8s_openapi::api::core::v1::Pod;
use kube::api::{Api, ListParams}; use kube::api::{Api, ApiResource, DynamicObject, ListParams};
use rand::RngCore; use rand::RngCore;
use rsa::pkcs1::EncodeRsaPublicKey; use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
use rsa::pkcs8::EncodePrivateKey;
use rsa::RsaPrivateKey; use rsa::RsaPrivateKey;
use serde::Deserialize; use serde::Deserialize;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@@ -73,7 +72,7 @@ fn gen_dkim_key_pair() -> (String, String) {
}; };
let public_key = private_key.to_public_key(); let public_key = private_key.to_public_key();
let public_pem = match public_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::LF) { let public_pem = match public_key.to_public_key_pem(rsa::pkcs8::LineEnding::LF) {
Ok(p) => p.to_string(), Ok(p) => p.to_string(),
Err(e) => { Err(e) => {
warn(&format!("Public key PEM encoding failed: {e}")); warn(&format!("Public key PEM encoding failed: {e}"));
@@ -643,7 +642,7 @@ async fn seed_openbao() -> Result<Option<SeedResult>> {
for (path, data) in all_paths { for (path, data) in all_paths {
if dirty_paths.contains(*path) { if dirty_paths.contains(*path) {
bao.kv_put("secret", path, data).await?; bao.kv_patch("secret", path, data).await?;
} }
} }
} }
@@ -739,16 +738,17 @@ async fn configure_db_engine(bao: &BaoClient) -> Result<()> {
let existing_vault_pass = bao.kv_get_field("secret", "vault", "pg-password").await?; let existing_vault_pass = bao.kv_get_field("secret", "vault", "pg-password").await?;
let vault_pg_pass = if existing_vault_pass.is_empty() { let vault_pg_pass = if existing_vault_pass.is_empty() {
rand_token() let new_pass = rand_token();
let mut vault_data = HashMap::new();
vault_data.insert("pg-password".to_string(), new_pass.clone());
bao.kv_put("secret", "vault", &vault_data).await?;
ok("vault KV entry written.");
new_pass
} else { } else {
ok("vault KV entry already present -- skipping write.");
existing_vault_pass existing_vault_pass
}; };
let mut vault_data = HashMap::new();
vault_data.insert("pg-password".to_string(), vault_pg_pass.clone());
bao.kv_put("secret", "vault", &vault_data).await?;
ok("vault KV entry written.");
let create_vault_sql = concat!( let create_vault_sql = concat!(
"DO $$ BEGIN ", "DO $$ BEGIN ",
"IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'vault') THEN ", "IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'vault') THEN ",
@@ -981,18 +981,31 @@ pub async fn cmd_seed() -> Result<()> {
let mut pg_pod = String::new(); let mut pg_pod = String::new();
let client = k::get_client().await?; let client = k::get_client().await?;
let ar = ApiResource {
group: "postgresql.cnpg.io".into(),
version: "v1".into(),
api_version: "postgresql.cnpg.io/v1".into(),
kind: "Cluster".into(),
plural: "clusters".into(),
};
let cnpg_api: Api<DynamicObject> = Api::namespaced_with(client.clone(), "data", &ar);
for _ in 0..60 { for _ in 0..60 {
let pods: Api<Pod> = Api::namespaced(client.clone(), "data"); if let Ok(cluster) = cnpg_api.get("postgres").await {
let lp = ListParams::default().labels("cnpg.io/cluster=postgres,role=primary"); let phase = cluster
if let Ok(pod_list) = pods.list(&lp).await { .data
if let Some(pod) = pod_list.items.first() { .get("status")
if let Some(name) = pod.metadata.name.as_deref() { .and_then(|s| s.get("phase"))
if pod .and_then(|p| p.as_str())
.status .unwrap_or("");
.as_ref() if phase == "Cluster in healthy state" {
.and_then(|s| s.phase.as_deref()) // Cluster is healthy — find the primary pod name
.unwrap_or("") let pods: Api<Pod> = Api::namespaced(client.clone(), "data");
== "Running" let lp = ListParams::default().labels("cnpg.io/cluster=postgres,role=primary");
if let Ok(pod_list) = pods.list(&lp).await {
if let Some(name) = pod_list
.items
.first()
.and_then(|p| p.metadata.name.as_deref())
{ {
pg_pod = name.to_string(); pg_pod = name.to_string();
ok(&format!("Postgres ready ({pg_pod}).")); ok(&format!("Postgres ready ({pg_pod})."));
@@ -1137,7 +1150,7 @@ pub async fn cmd_seed() -> Result<()> {
&gitea_admin_pass, &gitea_admin_pass,
"--must-change-password=false", "--must-change-password=false",
], ],
None, Some("gitea"),
) )
.await .await
{ {
@@ -1517,8 +1530,12 @@ mod tests {
"Private key should be PKCS8 PEM" "Private key should be PKCS8 PEM"
); );
assert!( assert!(
public_pem.contains("BEGIN RSA PUBLIC KEY"), public_pem.contains("BEGIN PUBLIC KEY"),
"Public key should be PEM" "Public key should be SPKI PEM (not PKCS#1)"
);
assert!(
!public_pem.contains("BEGIN RSA PUBLIC KEY"),
"Public key should NOT be PKCS#1 format"
); );
assert!(!private_pem.is_empty()); assert!(!private_pem.is_empty());
assert!(!public_pem.is_empty()); assert!(!public_pem.is_empty());
@@ -1576,7 +1593,7 @@ mod tests {
#[test] #[test]
fn test_dkim_public_key_extraction() { fn test_dkim_public_key_extraction() {
let pem = "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQ...\nbase64data\n-----END RSA PUBLIC KEY-----"; let pem = "-----BEGIN PUBLIC KEY-----\nMIIBCgKCAQ...\nbase64data\n-----END PUBLIC KEY-----";
let b64_key: String = pem let b64_key: String = pem
.replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "") .replace("-----END PUBLIC KEY-----", "")