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 ───────────────────────────────────────
/// 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<()> {
#[derive(Serialize)]
struct EnableRequest<'a> {

View File

@@ -6,10 +6,9 @@
use crate::error::{Result, ResultExt, SunbeamError};
use k8s_openapi::api::core::v1::Pod;
use kube::api::{Api, ListParams};
use kube::api::{Api, ApiResource, DynamicObject, ListParams};
use rand::RngCore;
use rsa::pkcs1::EncodeRsaPublicKey;
use rsa::pkcs8::EncodePrivateKey;
use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
use rsa::RsaPrivateKey;
use serde::Deserialize;
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_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(),
Err(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 {
if dirty_paths.contains(*path) {
bao.kv_put("secret", path, data).await?;
bao.kv_patch("secret", path, data).await?;
}
}
}
@@ -739,15 +738,16 @@ async fn configure_db_engine(bao: &BaoClient) -> Result<()> {
let existing_vault_pass = bao.kv_get_field("secret", "vault", "pg-password").await?;
let vault_pg_pass = if existing_vault_pass.is_empty() {
rand_token()
} else {
existing_vault_pass
};
let new_pass = rand_token();
let mut vault_data = HashMap::new();
vault_data.insert("pg-password".to_string(), vault_pg_pass.clone());
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 {
ok("vault KV entry already present -- skipping write.");
existing_vault_pass
};
let create_vault_sql = concat!(
"DO $$ BEGIN ",
@@ -981,18 +981,31 @@ pub async fn cmd_seed() -> Result<()> {
let mut pg_pod = String::new();
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 {
if let Ok(cluster) = cnpg_api.get("postgres").await {
let phase = cluster
.data
.get("status")
.and_then(|s| s.get("phase"))
.and_then(|p| p.as_str())
.unwrap_or("");
if phase == "Cluster in healthy state" {
// Cluster is healthy — find the primary pod name
let pods: Api<Pod> = Api::namespaced(client.clone(), "data");
let lp = ListParams::default().labels("cnpg.io/cluster=postgres,role=primary");
if let Ok(pod_list) = pods.list(&lp).await {
if let Some(pod) = pod_list.items.first() {
if let Some(name) = pod.metadata.name.as_deref() {
if pod
.status
.as_ref()
.and_then(|s| s.phase.as_deref())
.unwrap_or("")
== "Running"
if let Some(name) = pod_list
.items
.first()
.and_then(|p| p.metadata.name.as_deref())
{
pg_pod = name.to_string();
ok(&format!("Postgres ready ({pg_pod})."));
@@ -1137,7 +1150,7 @@ pub async fn cmd_seed() -> Result<()> {
&gitea_admin_pass,
"--must-change-password=false",
],
None,
Some("gitea"),
)
.await
{
@@ -1517,8 +1530,12 @@ mod tests {
"Private key should be PKCS8 PEM"
);
assert!(
public_pem.contains("BEGIN RSA PUBLIC KEY"),
"Public key should be PEM"
public_pem.contains("BEGIN PUBLIC KEY"),
"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!(!public_pem.is_empty());
@@ -1576,7 +1593,7 @@ mod tests {
#[test]
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
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")