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;