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:
@@ -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> {
|
||||||
|
|||||||
@@ -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,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 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();
|
||||||
} else {
|
|
||||||
existing_vault_pass
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut vault_data = HashMap::new();
|
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?;
|
bao.kv_put("secret", "vault", &vault_data).await?;
|
||||||
ok("vault KV entry written.");
|
ok("vault KV entry written.");
|
||||||
|
new_pass
|
||||||
|
} else {
|
||||||
|
ok("vault KV entry already present -- skipping write.");
|
||||||
|
existing_vault_pass
|
||||||
|
};
|
||||||
|
|
||||||
let create_vault_sql = concat!(
|
let create_vault_sql = concat!(
|
||||||
"DO $$ BEGIN ",
|
"DO $$ BEGIN ",
|
||||||
@@ -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 {
|
||||||
|
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 pods: Api<Pod> = Api::namespaced(client.clone(), "data");
|
||||||
let lp = ListParams::default().labels("cnpg.io/cluster=postgres,role=primary");
|
let lp = ListParams::default().labels("cnpg.io/cluster=postgres,role=primary");
|
||||||
if let Ok(pod_list) = pods.list(&lp).await {
|
if let Ok(pod_list) = pods.list(&lp).await {
|
||||||
if let Some(pod) = pod_list.items.first() {
|
if let Some(name) = pod_list
|
||||||
if let Some(name) = pod.metadata.name.as_deref() {
|
.items
|
||||||
if pod
|
.first()
|
||||||
.status
|
.and_then(|p| p.metadata.name.as_deref())
|
||||||
.as_ref()
|
|
||||||
.and_then(|s| s.phase.as_deref())
|
|
||||||
.unwrap_or("")
|
|
||||||
== "Running"
|
|
||||||
{
|
{
|
||||||
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-----", "")
|
||||||
|
|||||||
Reference in New Issue
Block a user