bindy/constants.rs
1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Global constants for the Bindy operator.
5//!
6//! This module contains all numeric and string constants used throughout the codebase.
7//! Constants are organized by category for easy maintenance.
8
9// ============================================================================
10// API Constants
11// ============================================================================
12
13/// API group for all Bindy DNS CRDs
14pub const API_GROUP: &str = "bindy.firestoned.io";
15
16/// API version for all Bindy DNS CRDs
17pub const API_VERSION: &str = "v1beta1";
18
19/// Fully qualified API version (group/version)
20pub const API_GROUP_VERSION: &str = "bindy.firestoned.io/v1beta1";
21
22/// Kind name for `DNSZone` resource
23pub const KIND_DNS_ZONE: &str = "DNSZone";
24
25/// Kind name for `ARecord` resource
26pub const KIND_A_RECORD: &str = "ARecord";
27
28/// Kind name for `AAAARecord` resource
29pub const KIND_AAAA_RECORD: &str = "AAAARecord";
30
31/// Kind name for `TXTRecord` resource
32pub const KIND_TXT_RECORD: &str = "TXTRecord";
33
34/// Kind name for `CNAMERecord` resource
35pub const KIND_CNAME_RECORD: &str = "CNAMERecord";
36
37/// Kind name for `MXRecord` resource
38pub const KIND_MX_RECORD: &str = "MXRecord";
39
40/// Kind name for `NSRecord` resource
41pub const KIND_NS_RECORD: &str = "NSRecord";
42
43/// Kind name for `SRVRecord` resource
44pub const KIND_SRV_RECORD: &str = "SRVRecord";
45
46/// Kind name for `CAARecord` resource
47pub const KIND_CAA_RECORD: &str = "CAARecord";
48
49/// Kind name for `Bind9Cluster` resource
50pub const KIND_BIND9_CLUSTER: &str = "Bind9Cluster";
51
52/// Kind name for `ClusterBind9Provider` resource
53pub const KIND_CLUSTER_BIND9_PROVIDER: &str = "ClusterBind9Provider";
54
55/// Kind name for `Bind9Instance` resource
56pub const KIND_BIND9_INSTANCE: &str = "Bind9Instance";
57
58// ============================================================================
59// DNS Protocol Constants
60// ============================================================================
61
62/// Standard DNS service port exposed externally
63pub const DNS_PORT: u16 = 53;
64
65/// DNS container port.
66///
67/// This is the port `named` binds inside the operand pod and the `targetPort`
68/// of the DNS Service (which still exposes the standard [`DNS_PORT`] `53` to
69/// clients). It is the **unprivileged** port `5353`, so non-root `named` binds
70/// it without the `NET_BIND_SERVICE` capability.
71///
72/// Cross-pod zone transfers stay coherent because bindcar (`0.7.2`+) accepts
73/// port-qualified endpoints (`<ip>:<port>`): the operator publishes secondary
74/// `primaries` and primary `also-notify` entries as `<ip>:5353`, and sets
75/// `NSUPDATE_PORT` on the bindcar sidecar so dynamic updates target this port.
76/// `allow-transfer` remains a bare-IP ACL (port-agnostic). See `build_pod_spec`,
77/// `render_listen_on`, and `crate::bind9::zone_ops`.
78pub const DNS_CONTAINER_PORT: u16 = 5353;
79
80/// Standard RNDC control port (non-privileged)
81pub const RNDC_PORT: u16 = 9530;
82
83/// Default bindcar HTTP API container port
84pub const BINDCAR_API_PORT: u16 = 8080;
85
86/// Default bindcar HTTP API service port (exposed via Kubernetes Service)
87pub const BINDCAR_SERVICE_PORT: u16 = 80;
88
89/// Default TTL for DNS records (5 minutes)
90pub const DEFAULT_DNS_RECORD_TTL_SECS: i32 = 300;
91
92/// Default TTL for zone files (1 hour)
93pub const DEFAULT_ZONE_TTL_SECS: u32 = 3600;
94
95/// Default SOA refresh interval (1 hour)
96pub const DEFAULT_SOA_REFRESH_SECS: u32 = 3600;
97
98/// Default SOA retry interval (10 minutes)
99pub const DEFAULT_SOA_RETRY_SECS: u32 = 600;
100
101/// Default SOA expire time (7 days)
102pub const DEFAULT_SOA_EXPIRE_SECS: u32 = 604_800;
103
104/// Default SOA negative TTL (1 day)
105pub const DEFAULT_SOA_NEGATIVE_TTL_SECS: u32 = 86400;
106
107/// TSIG fudge time in seconds (allows for clock skew)
108pub const TSIG_FUDGE_TIME_SECS: u64 = 300;
109
110// ============================================================================
111// Kubernetes Health Check Constants
112// ============================================================================
113
114/// Liveness probe initial delay (wait for BIND9 to start)
115pub const LIVENESS_INITIAL_DELAY_SECS: i32 = 30;
116
117/// Liveness probe period (how often to check)
118pub const LIVENESS_PERIOD_SECS: i32 = 10;
119
120/// Liveness probe timeout
121pub const LIVENESS_TIMEOUT_SECS: i32 = 5;
122
123/// Liveness probe failure threshold
124pub const LIVENESS_FAILURE_THRESHOLD: i32 = 3;
125
126/// Readiness probe initial delay
127pub const READINESS_INITIAL_DELAY_SECS: i32 = 10;
128
129/// Readiness probe period
130pub const READINESS_PERIOD_SECS: i32 = 5;
131
132/// Readiness probe timeout
133pub const READINESS_TIMEOUT_SECS: i32 = 3;
134
135/// Readiness probe failure threshold
136pub const READINESS_FAILURE_THRESHOLD: i32 = 3;
137
138// ============================================================================
139// Controller Error Handling Constants
140// ============================================================================
141
142/// Requeue duration for controller errors (30 seconds)
143pub const ERROR_REQUEUE_DURATION_SECS: u64 = 30;
144
145// ============================================================================
146// Leader Election Constants
147// ============================================================================
148
149/// Default leader election lease duration (15 seconds)
150pub const DEFAULT_LEASE_DURATION_SECS: u64 = 15;
151
152/// Default leader election renew deadline (10 seconds)
153pub const DEFAULT_LEASE_RENEW_DEADLINE_SECS: u64 = 10;
154
155/// Default leader election retry period (2 seconds)
156pub const DEFAULT_LEASE_RETRY_PERIOD_SECS: u64 = 2;
157
158// ============================================================================
159// BIND9 Version Constants
160// ============================================================================
161
162/// Default BIND9 version tag
163pub const DEFAULT_BIND9_VERSION: &str = "9.18";
164
165/// `ServiceAccount` name for BIND9 pods
166pub const BIND9_SERVICE_ACCOUNT: &str = "bind9";
167
168/// `MALLOC_CONF` environment variable value for BIND9 containers
169///
170/// Optimizes jemalloc memory decay for containerized environments:
171/// - `dirty_decay_ms:0` - Immediately return dirty pages to OS
172/// - `muzzy_decay_ms:0` - Immediately return muzzy pages to OS
173///
174/// This enables more aggressive memory reclamation in environments where
175/// memory pressure is monitored closely.
176pub const BIND9_MALLOC_CONF: &str = "dirty_decay_ms:0,muzzy_decay_ms:0";
177
178/// UID for running BIND9 and bindcar containers as non-root
179///
180/// This UID corresponds to the 'bind' or 'named' user in most BIND9 images.
181/// Running as non-root improves container security by following the principle
182/// of least privilege.
183pub const BIND9_NONROOT_UID: i64 = 101;
184
185// ============================================================================
186// Bindcar Container Constants
187// ============================================================================
188
189/// Default bindcar sidecar container image
190///
191/// This is the default image used for the bindcar HTTP API sidecar container
192/// when no image is specified in the `BindcarConfig` of a `Bind9Instance`,
193/// `Bind9Cluster`, or `ClusterBind9Provider`.
194pub const DEFAULT_BINDCAR_IMAGE: &str = "ghcr.io/firestoned/bindcar:v0.7.2";
195
196// ============================================================================
197// Bindcar Authentication Constants (Mode B — TokenReview)
198// ============================================================================
199
200/// `ServiceAccount` name the bindy operator runs as.
201///
202/// Under bindcar `0.7.0` Mode B (TokenReview), the operator presents its own SA
203/// token to the bindcar HTTP API, and bindcar validates the token's subject
204/// against its `BIND_ALLOWED_SERVICE_ACCOUNTS` allow-list. That allow-list must
205/// therefore name **this** (the caller's) SA, not the operand `bind9` SA.
206pub const OPERATOR_SERVICE_ACCOUNT: &str = "bindy";
207
208/// Default namespace the bindy operator runs in.
209///
210/// Used as a fallback when the `POD_NAMESPACE` environment variable is not set
211/// while composing the `BIND_ALLOWED_SERVICE_ACCOUNTS` value for the bindcar
212/// sidecar.
213pub const DEFAULT_OPERATOR_NAMESPACE: &str = "bindy-system";
214
215/// Audience that operator tokens must carry for bindcar `0.7.0` TokenReview.
216///
217/// bindcar verifies `status.audiences` against `BIND_TOKEN_AUDIENCES`
218/// (default `bindcar`). The operator projects a token with this audience (see
219/// `deploy/operator/deployment.yaml`) and the sidecar is configured with the
220/// matching `BIND_TOKEN_AUDIENCES`.
221pub const BINDCAR_TOKEN_AUDIENCE: &str = "bindcar";
222
223/// Writable temporary directory mounted into the bindcar sidecar.
224///
225/// Under Pod Security Admission `restricted` the sidecar runs with
226/// `readOnlyRootFilesystem: true`, but bindcar writes a `0600` TSIG key file for
227/// `nsupdate -k`. A memory-backed `emptyDir` is mounted here and `TMPDIR` points
228/// at it.
229pub const BINDCAR_TMP_PATH: &str = "/tmp";
230
231// ============================================================================
232// Container Name Constants
233// ============================================================================
234
235/// Name of the BIND9 container in the pod
236pub const CONTAINER_NAME_BIND9: &str = "bind9";
237
238/// Name of the bindcar API sidecar container in the pod
239pub const CONTAINER_NAME_BINDCAR: &str = "api";
240
241// ============================================================================
242// Runtime Constants
243// ============================================================================
244
245/// Number of worker threads for Tokio runtime
246pub const TOKIO_WORKER_THREADS: usize = 4;
247
248// ============================================================================
249// Replica Count Constants
250// ============================================================================
251
252/// Minimum number of replicas for testing
253pub const MIN_TEST_REPLICAS: i32 = 2;
254
255/// Maximum reasonable number of replicas for testing
256pub const MAX_TEST_REPLICAS: i32 = 10;
257
258// ============================================================================
259// Metrics Server Constants
260// ============================================================================
261
262/// Port for Prometheus metrics HTTP server
263pub const METRICS_SERVER_PORT: u16 = 8080;
264
265/// Path for Prometheus metrics endpoint
266pub const METRICS_SERVER_PATH: &str = "/metrics";
267
268/// Bind address for metrics HTTP server
269pub const METRICS_SERVER_BIND_ADDRESS: &str = "0.0.0.0";
270
271// ============================================================================
272// DNSZone Record Ownership Constants
273// ============================================================================
274
275/// Annotation key for marking which zone owns a DNS record
276///
277/// When a `DNSZone`'s label selector matches a DNS record, the `DNSZone` controller
278/// sets this annotation on the record with the value being the zone's FQDN.
279/// Record reconcilers read this annotation to determine which zone to update.
280pub const ANNOTATION_ZONE_OWNER: &str = "bindy.firestoned.io/zone";
281
282/// Annotation key for marking which zone previously owned a record
283///
284/// When a record stops matching a zone's selector, the `DNSZone` controller sets
285/// this annotation before removing the zone ownership. This helps track orphaned
286/// records and enables cleanup workflows.
287pub const ANNOTATION_ZONE_PREVIOUS_OWNER: &str = "bindy.firestoned.io/previous-zone";
288
289/// Annotation key on `Bind9Instance` that lists namespaces from which a
290/// `DNSZone` (in a *different* namespace) is permitted to target this
291/// instance via `spec.bind9InstancesFrom` selectors.
292///
293/// **F-003 mitigation.** A label-selector match alone is not enough to
294/// enrol a cross-namespace `Bind9Instance` in a zone — the platform admin
295/// who owns the instance must also annotate it with the zone's namespace.
296/// Same-namespace targeting (zone and instance in the same namespace) is
297/// always permitted and does not require this annotation.
298///
299/// Value format: comma-separated list of namespace names. The literal
300/// value `*` re-enables the pre-F-003 cluster-wide behaviour for
301/// platform admins who explicitly accept the risk.
302///
303/// Examples:
304/// - `"tenant-a,tenant-b"` — only zones in tenant-a or tenant-b may
305/// claim this instance.
306/// - `"*"` — any namespace may claim (back to pre-F-003 behaviour).
307/// - annotation absent — only same-namespace zones may claim.
308///
309/// Why an annotation rather than a CRD field on `ClusterBind9Provider`?
310/// The platform-admin contract for a cluster-wide operator is "platform
311/// admin labels their instances; tenants match those labels." The
312/// security gate must live on the side the tenant cannot forge — i.e.
313/// metadata on the platform-owned `Bind9Instance` — and an annotation
314/// keeps the admin's mental model intact without requiring tenants to
315/// add a `clusterRef` they had no reason to set previously.
316pub const ANNOTATION_ALLOW_ZONE_NAMESPACES: &str = "bindy.firestoned.io/allow-zone-namespaces";
317
318/// Wildcard value for [`ANNOTATION_ALLOW_ZONE_NAMESPACES`] meaning "any
319/// namespace may target this instance." Use with care — restores the
320/// pre-F-003 cluster-wide behaviour.
321pub const ALLOW_ZONE_NAMESPACES_WILDCARD: &str = "*";
322
323// ============================================================================
324// RNDC Key Rotation Constants
325// ============================================================================
326
327/// Annotation key for RNDC key creation timestamp (ISO 8601 format)
328///
329/// Tracks when the current RNDC key was created or last rotated.
330/// Used by the rotation reconciler to determine when rotation is due.
331///
332/// Example value: `"2025-01-26T10:00:00Z"`
333pub const ANNOTATION_RNDC_CREATED_AT: &str = "bindy.firestoned.io/rndc-created-at";
334
335/// Annotation key for RNDC key rotation timestamp (ISO 8601 format)
336///
337/// Tracks when the RNDC key should be rotated next.
338/// Calculated as: `created_at + rotate_after`
339///
340/// Only present when `auto_rotate` is enabled.
341///
342/// Example value: `"2025-02-25T10:00:00Z"` (30 days after creation)
343pub const ANNOTATION_RNDC_ROTATE_AT: &str = "bindy.firestoned.io/rndc-rotate-at";
344
345/// Annotation key for RNDC key rotation count
346///
347/// Tracks the number of times the RNDC key has been rotated.
348/// Starts at `0` for newly-created keys and increments on each rotation.
349///
350/// Example value: `"5"` (key has been rotated 5 times)
351pub const ANNOTATION_RNDC_ROTATION_COUNT: &str = "bindy.firestoned.io/rndc-rotation-count";
352
353/// Annotation key for tracking pod restarts after RNDC rotation
354///
355/// Added to Deployment pod template to trigger rolling restart when RNDC key is rotated.
356/// Value is the timestamp when rotation occurred (ISO 8601 format).
357///
358/// Example value: `"2025-01-26T10:30:00Z"`
359pub const ANNOTATION_RNDC_ROTATED_AT: &str = "bindy.firestoned.io/rndc-rotated-at";
360
361/// Minimum rotation interval in hours (1 hour)
362///
363/// RNDC keys cannot be rotated more frequently than once per hour.
364/// This prevents infinite reconciliation loops and rate-limits rotation operations.
365pub const MIN_ROTATION_INTERVAL_HOURS: u64 = 1;
366
367/// Maximum rotation interval in hours (8760 hours = 365 days = 1 year)
368///
369/// RNDC keys must be rotated at least once per year for security compliance.
370/// This is the upper bound for the `rotate_after` configuration.
371pub const MAX_ROTATION_INTERVAL_HOURS: u64 = 8760;
372
373/// Default rotation interval (720 hours = 30 days)
374///
375/// Default value for the `rotate_after` field when `auto_rotate` is enabled.
376/// Balances security (regular rotation) with operational stability (not too frequent).
377///
378/// This is specified as a Go duration string: `"720h"`
379pub const DEFAULT_ROTATION_INTERVAL: &str = "720h";
380
381/// Minimum time between rotations in hours (1 hour)
382///
383/// Even if rotation is due (based on `rotate_at` timestamp), the reconciler
384/// will not rotate a key if it was created or rotated within the last hour.
385///
386/// This prevents rapid successive rotations in edge cases (e.g., clock skew,
387/// manual timestamp manipulation, reconciliation loops).
388pub const MIN_TIME_BETWEEN_ROTATIONS_HOURS: i64 = 1;
389
390// ============================================================================
391// Kubernetes API Client Rate Limiting Constants
392// ============================================================================
393
394/// Kubernetes API client queries per second (sustained rate)
395///
396/// This matches kubectl default rate limits and has been tested at scale.
397/// Prevents overwhelming the API server with too many requests.
398/// Can be overridden via `BINDY_KUBE_QPS` environment variable.
399pub const KUBE_CLIENT_QPS: f32 = 20.0;
400
401/// Kubernetes API client burst size (max concurrent requests)
402///
403/// Allows temporary bursts above the QPS limit for reconciliation spikes.
404/// Matches kubectl defaults for optimal API server behavior.
405/// Can be overridden via `BINDY_KUBE_BURST` environment variable.
406pub const KUBE_CLIENT_BURST: u32 = 30;
407
408/// Page size for Kubernetes API list operations
409///
410/// Balances memory usage vs. number of API calls.
411/// Limits each list response to 100 items, reducing memory pressure
412/// when listing large resource sets (e.g., 1000+ `DNSZone`s).
413///
414/// With 100 items per page:
415/// - 1000 resources = 10 API calls
416/// - Memory usage remains constant (O(1) relative to total count)
417/// - Reduces API server load per request
418pub const KUBE_LIST_PAGE_SIZE: u32 = 100;
419
420// ============================================================================
421// User-volume Allow-list (F-001 mitigation)
422// ============================================================================
423//
424// `Bind9Instance` and `Bind9Cluster` accept user-supplied `volumes` and
425// `volumeMounts` fields that flow into the managed Pod spec. To prevent a
426// namespace-tenant from mounting `hostPath`, `csi`, foreign Secrets, or any
427// volume into a container the operator stamps with cluster-wide RBAC, we
428// validate every user-supplied volume against the constants below before
429// constructing the Pod. See `src/safe_volume.rs`.
430
431/// Mount-path prefixes allowed for user-supplied `volumeMounts`.
432///
433/// Anything outside these prefixes is rejected at reconcile time. Operator-
434/// managed mounts (`/etc/bind/...`, `/var/cache/bind`) are added by the
435/// resource builder and bypass this check.
436pub const ALLOWED_USER_MOUNT_PREFIXES: &[&str] = &["/data/", "/var/log/bind/"];
437
438/// Required name prefix for any Secret that the user references via a
439/// `secret:` volume. Prevents the user from mounting an arbitrary Secret
440/// (including the operator's own credentials) into the BIND9/bindcar pod.
441pub const ALLOWED_USER_SECRET_PREFIX: &str = "bindy-";
442
443/// Required name prefix for any PVC that the user references via a
444/// `persistentVolumeClaim:` volume. Same rationale as
445/// [`ALLOWED_USER_SECRET_PREFIX`].
446pub const ALLOWED_USER_PVC_PREFIX: &str = "bindy-";
447
448/// Required name prefix for any ConfigMap that the user references via a
449/// `configMap:` volume. Same rationale as [`ALLOWED_USER_SECRET_PREFIX`].
450pub const ALLOWED_USER_CONFIGMAP_PREFIX: &str = "bindy-";