kaniop_operator/crd.rs
1use kaniop_k8s_util::types::get_first_cloned;
2
3use kanidm_proto::{
4 constants::{ATTR_GIDNUMBER, ATTR_LOGINSHELL},
5 v1::Entry,
6};
7
8#[cfg(feature = "schemars")]
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12/// Configuration for automatic secret rotation.
13///
14/// When enabled, the operator will automatically rotate secrets based on the configured period.
15/// This is useful for security compliance and reducing the impact of credential leakage.
16#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
17#[cfg_attr(feature = "schemars", derive(JsonSchema))]
18#[serde(rename_all = "camelCase")]
19pub struct SecretRotation {
20 /// Enable automatic secret rotation. Defaults to false (opt-in).
21 #[serde(default)]
22 pub enabled: bool,
23
24 /// Rotation period in days. Secrets will be rotated when they are older than this period.
25 /// Defaults to 90 days.
26 #[serde(default = "default_rotation_period_days")]
27 pub period_days: u32,
28}
29
30impl Default for SecretRotation {
31 fn default() -> Self {
32 Self {
33 enabled: false,
34 period_days: default_rotation_period_days(),
35 }
36 }
37}
38
39/// Default rotation period of 90 days.
40///
41/// This value aligns with common security best practices and compliance frameworks
42/// (e.g., PCI-DSS, SOC 2) that recommend rotating credentials every 90 days.
43fn default_rotation_period_days() -> u32 {
44 90
45}
46
47/// Checks if a given value is equal to its type's default value.
48pub fn is_default<T: Default + PartialEq>(value: &T) -> bool {
49 #[cfg(feature = "examples-gen")]
50 {
51 // When generating examples, never skip fields so users can see all available options
52 let _ = value; // Suppress unused variable warning
53 false
54 }
55 #[cfg(not(feature = "examples-gen"))]
56 {
57 value == &T::default()
58 }
59}
60
61/// KanidmRef is a reference to a Kanidm object in the same cluster. It is used to specify where
62/// the object is stored.
63#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
64#[cfg_attr(feature = "schemars", derive(JsonSchema))]
65#[schemars(extend("x-kubernetes-validations" = [{"message": "Value is immutable", "rule": "self == oldSelf"}]))]
66#[serde(rename_all = "camelCase")]
67pub struct KanidmRef {
68 pub name: String,
69
70 /// For cross-namespace resources. Reference Kanidm namespace. If omitted, the namespace of the
71 /// resource will be used.
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub namespace: Option<String>,
74}
75
76/// Kanidm has features that enable its accounts and groups to be consumed on POSIX-like machines,
77/// such as Linux, FreeBSD or others. Both service accounts and person accounts can be used on POSIX
78/// systems.
79///
80/// The attributes defined here are set by the operator. If you want to manage those attributes
81/// from the database, do not set them here.
82/// Additionally, if you unset them here, they will be kept in the database.
83#[derive(Serialize, Deserialize, Clone, Debug, Default)]
84#[cfg_attr(feature = "schemars", derive(JsonSchema))]
85#[serde(rename_all = "camelCase")]
86pub struct KanidmAccountPosixAttributes {
87 /// The group ID number (GID) for the person account. In Kanidm there is no difference between
88 /// a UID and a GID number.
89 ///
90 /// If omitted, Kanidm will generate it automatically.
91 ///
92 /// More info:
93 /// https://kanidm.github.io/kanidm/stable/accounts/posix_accounts_and_groups.html#uid-and-gid-numbers
94 pub gidnumber: Option<u32>,
95 /// The login shell for the person account.
96 ///
97 /// This sets the default shell that will be used when the user logs in via SSH or other
98 /// mechanisms that require a shell. Common values include /bin/bash, /bin/zsh, /bin/sh.
99 pub loginshell: Option<String>,
100}
101
102impl PartialEq for KanidmAccountPosixAttributes {
103 /// Compare attributes defined in the first object with the second object values.
104 /// If the second object has more attributes defined, they will be ignored.
105 fn eq(&self, other: &Self) -> bool {
106 (self.gidnumber.is_none() || self.gidnumber == other.gidnumber)
107 && (self.loginshell.is_none() || self.loginshell == other.loginshell)
108 }
109}
110
111impl From<Entry> for KanidmAccountPosixAttributes {
112 fn from(entry: Entry) -> Self {
113 KanidmAccountPosixAttributes {
114 gidnumber: get_first_cloned(&entry, ATTR_GIDNUMBER).and_then(|s| s.parse::<u32>().ok()),
115 loginshell: get_first_cloned(&entry, ATTR_LOGINSHELL),
116 }
117 }
118}