bindy/bind9/types.rs
1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Types and constants for BIND9 management.
5
6/// RNDC key data for authentication.
7#[derive(Clone)]
8pub struct RndcKeyData {
9 /// Key name (typically the instance name)
10 pub name: String,
11 /// HMAC algorithm
12 pub algorithm: crate::crd::RndcAlgorithm,
13 /// Base64-encoded secret key
14 pub secret: String,
15}
16
17// Custom Debug implementation to prevent logging the secret in cleartext
18impl std::fmt::Debug for RndcKeyData {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 f.debug_struct("RndcKeyData")
21 .field("name", &self.name)
22 .field("algorithm", &self.algorithm)
23 .field("secret", &"<redacted>")
24 .finish()
25 }
26}
27
28/// RNDC command error with structured information.
29///
30/// Parses BIND9 RNDC error responses in the format:
31/// ```text
32/// rndc: 'command' failed: error_type
33/// error details
34/// ```
35#[derive(Debug, Clone, thiserror::Error)]
36#[error("RNDC command '{command}' failed: {error}")]
37pub struct RndcError {
38 /// The RNDC command that failed (e.g., "zonestatus", "addzone")
39 pub command: String,
40 /// The error type (e.g., "not found", "already exists")
41 pub error: String,
42 /// Additional error details from BIND9
43 pub details: Option<String>,
44}
45
46impl RndcError {
47 /// Parse an RNDC error response.
48 ///
49 /// Expected format:
50 /// ```text
51 /// rndc: 'zonestatus' failed: not found
52 /// no matching zone 'example.com' in any view
53 /// ```
54 #[must_use]
55 pub fn parse(response: &str) -> Option<Self> {
56 // Parse first line: rndc: 'command' failed: error
57 let lines: Vec<&str> = response.lines().collect();
58 let first_line = lines.first()?;
59
60 if !first_line.starts_with("rndc:") {
61 return None;
62 }
63
64 // Extract command from 'command'
65 let command_start = first_line.find('\'')?;
66 let command_end = first_line[command_start + 1..].find('\'')?;
67 let command = first_line[command_start + 1..command_start + 1 + command_end].to_string();
68
69 // Extract error after "failed: "
70 let failed_pos = first_line.find("failed:")?;
71 let error = first_line[failed_pos + 7..].trim().to_string();
72
73 // Remaining lines are details
74 let details = if lines.len() > 1 {
75 Some(lines[1..].join("\n").trim().to_string())
76 } else {
77 None
78 };
79
80 Some(Self {
81 command,
82 error,
83 details,
84 })
85 }
86}
87
88/// Path to the default `ServiceAccount` token file in Kubernetes pods.
89///
90/// This token carries the API server's default audience. bindcar `0.7.0`
91/// enforces `status.audiences` on the TokenReview response, so this default
92/// token is **rejected** by bindcar unless it also carries the `bindcar`
93/// audience. Prefer [`BINDCAR_TOKEN_PATH`] (an explicitly-projected token with
94/// `audience: bindcar`); this path is kept only as a backward-compatible
95/// fallback for clusters that have not yet projected the audience-scoped token.
96pub const SERVICE_ACCOUNT_TOKEN_PATH: &str = "/var/run/secrets/kubernetes.io/serviceaccount/token";
97
98/// Path to the projected `ServiceAccount` token minted with the `bindcar`
99/// audience (see `deploy/operator/deployment.yaml`).
100///
101/// bindcar `0.7.0` verifies the token's `status.audiences` against
102/// `BIND_TOKEN_AUDIENCES` (default `bindcar`), so the operator must present a
103/// token whose audience is `bindcar` rather than the default API-server
104/// audience. This path is read in preference to [`SERVICE_ACCOUNT_TOKEN_PATH`].
105pub const BINDCAR_TOKEN_PATH: &str = "/var/run/secrets/bindcar/token";
106
107/// Parameters for creating SRV records.
108///
109/// Contains the priority, weight, port, and target required for SRV records.
110#[derive(Clone)]
111pub struct SRVRecordData {
112 /// Priority of the target host (lower is higher priority)
113 pub priority: i32,
114 /// Relative weight for records with the same priority
115 pub weight: i32,
116 /// TCP or UDP port on which the service is found
117 pub port: i32,
118 /// Canonical hostname of the machine providing the service
119 pub target: String,
120 /// Time to live in seconds
121 pub ttl: Option<i32>,
122}
123
124#[cfg(test)]
125#[path = "types_tests.rs"]
126mod types_tests;