fix: CIDR suffix in agent URLs, agent client CIDR strip, and IP SAN fixes
Some checks failed
CI Pipeline / Rust Format Check (push) Failing after 5s
CI Pipeline / Clippy Lints (push) Successful in 45s
CI Pipeline / Rust Unit Tests (push) Successful in 1m1s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 12s
CI Pipeline / Build .deb & Release (push) Has been skipped
Some checks failed
CI Pipeline / Rust Format Check (push) Failing after 5s
CI Pipeline / Clippy Lints (push) Successful in 45s
CI Pipeline / Rust Unit Tests (push) Successful in 1m1s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 12s
CI Pipeline / Build .deb & Release (push) Has been skipped
BUG-10: PostgreSQL inet type includes CIDR suffix (/32) when cast to text, causing malformed agent URLs like https://127.0.0.1/32:12443. Fixed by using host(ip_address)::text in all SQL queries across pm-worker and pm-web modules, and adding a Rust-side safety strip of CIDR notation in pm-agent-client. Files changed: - crates/pm-agent-client/src/client.rs: strip CIDR suffix from IP - crates/pm-worker/src/health_poller.rs: host(ip_address)::text - crates/pm-worker/src/patch_poller.rs: host(ip_address)::text - crates/pm-worker/src/refresh_listener.rs: host(ip_address)::text - crates/pm-worker/src/job_executor.rs: host(ip_address)::text (2 places) - crates/pm-worker/src/ws_relay.rs: host(h.ip_address)::text - crates/pm-web/src/routes/discovery.rs: host(ip_address)::text (2 places) - crates/pm-web/src/routes/hosts.rs: host(ip_address)::text (3 places) - docs/linux_patch_api_research.md: added research notes
This commit is contained in:
@ -107,8 +107,8 @@ impl AgentClient {
|
||||
.build()
|
||||
.map_err(|e| AgentClientError::Request(e))?;
|
||||
|
||||
let base_url = format!("https://{}:{}/api/v1", host_ip, port);
|
||||
tracing::debug!(base_url = %base_url, "AgentClient created");
|
||||
let clean_ip = host_ip.split('/').next().unwrap_or(host_ip);
|
||||
let base_url = format!("https://{}:{}/api/v1", clean_ip, port);
|
||||
|
||||
Ok(Self { inner, base_url })
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ async fn get_scan_results(
|
||||
Path(scan_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<DiscoveryResult>>, (StatusCode, Json<Value>)> {
|
||||
sqlx::query_as::<_, DiscoveryResult>(
|
||||
r#"SELECT id, scan_id, ip_address::text AS ip_address, fqdn,
|
||||
r#"SELECT id, scan_id, host(ip_address)::text AS ip_address, fqdn,
|
||||
agent_version, os_name, agent_port, discovered_at, registered
|
||||
FROM discovery_results
|
||||
WHERE scan_id = $1
|
||||
@ -230,7 +230,7 @@ async fn register_discovered_host(
|
||||
|
||||
// Fetch discovery result
|
||||
let result: Option<DiscoveryResult> = sqlx::query_as(
|
||||
r#"SELECT id, scan_id, ip_address::text AS ip_address, fqdn,
|
||||
r#"SELECT id, scan_id, host(ip_address)::text AS ip_address, fqdn,
|
||||
agent_version, os_name, agent_port, discovered_at, registered
|
||||
FROM discovery_results WHERE id = $1"#,
|
||||
)
|
||||
|
||||
@ -109,7 +109,7 @@ async fn list_hosts(
|
||||
let hosts: Vec<HostSummary> = if auth.role.is_admin() {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, fqdn, ip_address::text AS ip_address, display_name,
|
||||
SELECT id, fqdn, host(ip_address)::text AS ip_address, display_name,
|
||||
os_family, os_name, health_status, agent_version, registered_at
|
||||
FROM hosts
|
||||
ORDER BY fqdn
|
||||
@ -123,7 +123,7 @@ async fn list_hosts(
|
||||
} else {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT DISTINCT h.id, h.fqdn, h.ip_address::text AS ip_address,
|
||||
SELECT DISTINCT h.id, h.fqdn, host(h.ip_address)::text AS ip_address,
|
||||
h.display_name, h.os_family, h.os_name,
|
||||
h.health_status, h.agent_version, h.registered_at
|
||||
FROM hosts h
|
||||
@ -275,7 +275,7 @@ async fn get_host(
|
||||
let host: Option<Value> = sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT row_to_json(h) FROM (
|
||||
SELECT id, fqdn, ip_address::text AS ip_address, display_name,
|
||||
SELECT id, fqdn, host(ip_address)::text AS ip_address, display_name,
|
||||
os_family, os_name, arch, agent_version, health_status,
|
||||
last_health_at, last_patch_at, agent_port, notes,
|
||||
registered_at, updated_at
|
||||
|
||||
@ -51,7 +51,7 @@ pub async fn run_health_poller(pool: PgPool, config: Arc<AppConfig>) {
|
||||
|
||||
// Fetch all hosts.
|
||||
let hosts: Vec<HostRow> = match sqlx::query_as(
|
||||
"SELECT id, ip_address::text AS ip_address, agent_port FROM hosts ORDER BY id",
|
||||
"SELECT id, host(ip_address)::text AS ip_address, agent_port FROM hosts ORDER BY id",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
|
||||
@ -344,7 +344,7 @@ async fn execute_host_job(
|
||||
|
||||
// ── 1. Fetch host connection details ─────────────────────────────────────
|
||||
let host: HostRow = match sqlx::query_as(
|
||||
"SELECT ip_address::text AS ip_address, agent_port FROM hosts WHERE id = $1",
|
||||
"SELECT host(ip_address)::text AS ip_address, agent_port FROM hosts WHERE id = $1",
|
||||
)
|
||||
.bind(host_id)
|
||||
.fetch_optional(&pool)
|
||||
@ -480,7 +480,7 @@ pub async fn poll_running_jobs(pool: PgPool, config: Arc<AppConfig>) {
|
||||
SELECT pjh.id,
|
||||
pjh.agent_job_id,
|
||||
pjh.job_id,
|
||||
h.ip_address::text AS ip_address,
|
||||
host(h.ip_address)::text AS ip_address,
|
||||
h.agent_port
|
||||
FROM patch_job_hosts pjh
|
||||
JOIN hosts h ON h.id = pjh.host_id
|
||||
|
||||
@ -49,7 +49,7 @@ pub async fn run_patch_poller(pool: PgPool, config: Arc<AppConfig>) {
|
||||
let ca_cert = Arc::new(certs.ca_cert);
|
||||
|
||||
let hosts: Vec<HostRow> = match sqlx::query_as(
|
||||
"SELECT id, ip_address::text AS ip_address, agent_port FROM hosts ORDER BY id",
|
||||
"SELECT id, host(ip_address)::text AS ip_address, agent_port FROM hosts ORDER BY id",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
|
||||
@ -69,7 +69,7 @@ async fn listen_loop(pool: &PgPool, config: &AppConfig) -> anyhow::Result<()> {
|
||||
|
||||
// Fetch the host from the database.
|
||||
let host: Option<HostRow> = sqlx::query_as(
|
||||
"SELECT id, ip_address::text AS ip_address, agent_port FROM hosts WHERE id = $1",
|
||||
"SELECT id, host(ip_address)::text AS ip_address, agent_port FROM hosts WHERE id = $1",
|
||||
)
|
||||
.bind(host_id)
|
||||
.fetch_optional(pool)
|
||||
|
||||
@ -138,7 +138,7 @@ async fn query_running_jobs(pool: &PgPool) -> anyhow::Result<Vec<RunningHostJob>
|
||||
pjh.job_id,
|
||||
pjh.host_id,
|
||||
pjh.agent_job_id,
|
||||
COALESCE(h.fqdn, h.ip_address::text) AS host_address
|
||||
COALESCE(h.fqdn, host(h.ip_address)::text) AS host_address
|
||||
FROM patch_job_hosts pjh
|
||||
JOIN hosts h ON h.id = pjh.host_id
|
||||
WHERE pjh.status = 'running'::job_status
|
||||
|
||||
Reference in New Issue
Block a user