1use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::flex;
8
9#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
11#[serde(deny_unknown_fields)]
12pub struct SecretDef {
13 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub file: Option<String>,
16
17 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub environment: Option<String>,
21
22 #[serde(
25 default,
26 deserialize_with = "flex::bool",
27 skip_serializing_if = "std::ops::Not::not"
28 )]
29 #[schemars(with = "flex::BoolOrString")]
30 pub external: bool,
31
32 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub name: Option<String>,
36
37 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub age: Option<String>,
41
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub driver: Option<String>,
45
46 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub refresh: Option<String>,
51
52 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub on_change: Option<OnChange>,
57
58 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub rotate: Option<Vec<String>>,
67}
68
69#[derive(
73 Debug,
74 Clone,
75 Copy,
76 Default,
77 PartialEq,
78 Eq,
79 PartialOrd,
80 Ord,
81 Hash,
82 Serialize,
83 Deserialize,
84 JsonSchema,
85)]
86#[serde(rename_all = "lowercase")]
87pub enum OnChange {
88 None,
92 Restart,
96 #[default]
99 Roll,
100}
101
102impl OnChange {
103 pub fn as_str(self) -> &'static str {
104 match self {
105 OnChange::None => "none",
106 OnChange::Restart => "restart",
107 OnChange::Roll => "roll",
108 }
109 }
110}
111
112impl std::fmt::Display for OnChange {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 f.write_str(self.as_str())
115 }
116}
117
118impl SecretDef {
119 pub fn validate(&self) -> std::result::Result<(), String> {
122 let sources = [
123 self.file.is_some(),
124 self.environment.is_some(),
125 self.external,
126 self.age.is_some(),
127 self.driver.is_some(),
128 ];
129 if sources.iter().filter(|s| **s).count() != 1 {
130 return Err(
131 "needs exactly one of file, environment, external, age, or driver (with name)"
132 .into(),
133 );
134 }
135 if self.name.is_some() && !self.external && self.driver.is_none() {
136 return Err("name goes with external or driver".into());
137 }
138 if self.driver.is_some() && self.name.as_deref().is_none_or(str::is_empty) {
139 return Err("driver needs name: the driver's reference to the secret".into());
140 }
141 if self.external {
142 if let Some(n) = &self.name {
143 crate::secrets::validate_name(n).map_err(|e| e.to_string())?;
144 }
145 }
146 if self.age.as_deref().is_some_and(|a| a.trim().is_empty()) {
147 return Err("age is empty".into());
148 }
149 if self
150 .rotate
151 .as_ref()
152 .is_some_and(|a| a.is_empty() || a[0].is_empty())
153 {
154 return Err("rotate needs a command: [argv...]".into());
155 }
156 if let Some(r) = &self.refresh {
157 if self.driver.is_none() {
158 return Err("refresh goes with driver".into());
159 }
160 let d = flex::parse_duration(r).map_err(|e| format!("refresh: {e}"))?;
161 if d < std::time::Duration::from_secs(10) {
162 return Err(format!("refresh {r:?}: at least 10s"));
163 }
164 }
165 Ok(())
166 }
167
168 pub fn store_name<'a>(&'a self, key: &'a str) -> Option<&'a str> {
170 self.external.then(|| self.name.as_deref().unwrap_or(key))
171 }
172
173 pub fn refresh_interval(&self) -> std::time::Duration {
175 self.refresh
176 .as_deref()
177 .and_then(|r| flex::parse_duration(r).ok())
178 .unwrap_or(DEFAULT_SECRET_REFRESH)
179 }
180
181 pub fn is_client_side(&self) -> bool {
184 self.file.is_some() || self.environment.is_some()
185 }
186
187 pub fn source_kind(&self) -> &'static str {
189 if self.file.is_some() {
190 "file"
191 } else if self.environment.is_some() {
192 "environment"
193 } else if self.external {
194 "external"
195 } else if self.age.is_some() {
196 "age"
197 } else if self.driver.is_some() {
198 "driver"
199 } else {
200 "none"
201 }
202 }
203}
204
205pub const DEFAULT_SECRET_REFRESH: std::time::Duration = std::time::Duration::from_secs(3600);