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