bindy/reconcilers/bind9instance/config.rs
1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! RNDC configuration precedence resolution.
5//!
6//! Resolves RNDC key configuration from multiple sources following the precedence order:
7//! 1. Instance level (`Bind9InstanceSpec.rndc_key`)
8//! 2. Role level (`PrimaryConfig.rndc_key` or `SecondaryConfig.rndc_key`)
9//! 3. Global level (`Bind9Config.rndc_secret_ref` - deprecated)
10//! 4. Default (auto-generated with defaults from constants)
11
12use crate::constants::DEFAULT_ROTATION_INTERVAL;
13use crate::crd::{RndcAlgorithm, RndcKeyConfig, RndcSecretRef, ServerRole};
14
15/// Resolve RNDC configuration from multiple sources following precedence order.
16///
17/// # Precedence Order
18///
19/// 1. **Instance level** - Highest priority (overrides all others)
20/// 2. **Role level** - Primary or Secondary role configuration
21/// 3. **Global level** - Cluster-wide configuration
22/// 4. **Default** - Auto-generated with default settings
23///
24/// # Arguments
25///
26/// * `instance_config` - Instance-level RNDC configuration (`Bind9InstanceSpec.rndc_key`)
27/// * `role_config` - Role-level RNDC configuration (`PrimaryConfig.rndc_key` or `SecondaryConfig.rndc_key`)
28/// * `global_config` - Global RNDC configuration (from `Bind9ClusterCommonSpec`)
29///
30/// # Returns
31///
32/// Resolved `RndcKeyConfig` with the highest-precedence configuration applied.
33///
34/// # Examples
35///
36/// ```rust,no_run
37/// use bindy::crd::{RndcKeyConfig, RndcAlgorithm};
38/// use bindy::reconcilers::bind9instance::config::resolve_rndc_config;
39///
40/// let instance_config = Some(RndcKeyConfig {
41/// auto_rotate: true,
42/// rotate_after: "30d".to_string(),
43/// secret_ref: None,
44/// secret: None,
45/// algorithm: RndcAlgorithm::HmacSha256,
46/// });
47///
48/// let resolved = resolve_rndc_config(instance_config.as_ref(), None, None);
49/// assert!(resolved.auto_rotate);
50/// ```
51#[must_use]
52pub fn resolve_rndc_config(
53 instance_config: Option<&RndcKeyConfig>,
54 role_config: Option<&RndcKeyConfig>,
55 global_config: Option<&RndcKeyConfig>,
56) -> RndcKeyConfig {
57 // Precedence order: Instance > Role > Global > Default
58 if let Some(config) = instance_config {
59 return config.clone();
60 }
61
62 if let Some(config) = role_config {
63 return config.clone();
64 }
65
66 if let Some(config) = global_config {
67 return config.clone();
68 }
69
70 // Default: auto-generated with no rotation
71 RndcKeyConfig {
72 auto_rotate: false,
73 rotate_after: DEFAULT_ROTATION_INTERVAL.to_string(),
74 secret_ref: None,
75 secret: None,
76 algorithm: RndcAlgorithm::default(),
77 }
78}
79
80/// Resolve RNDC configuration with backward compatibility for deprecated fields.
81///
82/// Handles migration from the deprecated `rndc_secret_ref` field to the new `rndc_keys` field.
83/// If both are present, `rndc_keys` takes precedence.
84///
85/// # Arguments
86///
87/// * `rndc_keys` - New RNDC configuration field (preferred)
88/// * `rndc_secret_ref` - Deprecated RNDC secret reference (for backward compatibility)
89/// * `role` - Server role (Primary or Secondary)
90///
91/// # Returns
92///
93/// Resolved `RndcKeyConfig`. If only the deprecated field is present, converts it to the new format.
94///
95/// # Examples
96///
97/// ```rust,no_run
98/// use bindy::crd::{RndcKeyConfig, RndcSecretRef, RndcAlgorithm, ServerRole};
99/// use bindy::reconcilers::bind9instance::config::resolve_rndc_config_from_deprecated;
100///
101/// // New field takes precedence
102/// let new_config = Some(RndcKeyConfig {
103/// auto_rotate: true,
104/// rotate_after: "30d".to_string(),
105/// secret_ref: None,
106/// secret: None,
107/// algorithm: RndcAlgorithm::HmacSha256,
108/// });
109///
110/// let resolved = resolve_rndc_config_from_deprecated(new_config.as_ref(), None, ServerRole::Primary);
111/// assert!(resolved.auto_rotate);
112/// ```
113#[must_use]
114pub fn resolve_rndc_config_from_deprecated(
115 rndc_keys: Option<&RndcKeyConfig>,
116 rndc_secret_ref: Option<&RndcSecretRef>,
117 _role: ServerRole,
118) -> RndcKeyConfig {
119 // New field takes precedence
120 if let Some(config) = rndc_keys {
121 return config.clone();
122 }
123
124 // Fall back to deprecated field if present
125 if let Some(secret_ref) = rndc_secret_ref {
126 // Convert deprecated RndcSecretRef to new RndcKeyConfig
127 return RndcKeyConfig {
128 auto_rotate: false, // No rotation for user-managed secrets
129 rotate_after: DEFAULT_ROTATION_INTERVAL.to_string(),
130 secret_ref: Some(secret_ref.clone()),
131 secret: None,
132 algorithm: secret_ref.algorithm.clone(),
133 };
134 }
135
136 // Default: auto-generated with no rotation
137 RndcKeyConfig {
138 auto_rotate: false,
139 rotate_after: DEFAULT_ROTATION_INTERVAL.to_string(),
140 secret_ref: None,
141 secret: None,
142 algorithm: RndcAlgorithm::default(),
143 }
144}
145
146#[cfg(test)]
147#[path = "config_tests.rs"]
148mod config_tests;