rustfs_policy/auth/
credentials.rs1use crate::error::Error as IamError;
16use crate::error::{Error, Result};
17use crate::policy::{INHERITED_POLICY_TYPE, Policy, Validator, iam_policy_claim_name_sa};
18use crate::utils;
19use crate::utils::extract_claims;
20use serde::de::DeserializeOwned;
21use serde::{Deserialize, Serialize};
22use serde_json::{Value, json};
23use std::collections::HashMap;
24use time::OffsetDateTime;
25use time::macros::offset;
26
27const ACCESS_KEY_MIN_LEN: usize = 3;
28const ACCESS_KEY_MAX_LEN: usize = 20;
29const SECRET_KEY_MIN_LEN: usize = 8;
30const SECRET_KEY_MAX_LEN: usize = 40;
31
32pub const ACCOUNT_ON: &str = "on";
33pub const ACCOUNT_OFF: &str = "off";
34
35const RESERVED_CHARS: &str = "=,";
36
37pub fn contains_reserved_chars(s: &str) -> bool {
39 s.contains(RESERVED_CHARS)
40}
41
42pub fn is_access_key_valid(access_key: &str) -> bool {
44 access_key.len() >= ACCESS_KEY_MIN_LEN
45}
46
47pub fn is_secret_key_valid(secret_key: &str) -> bool {
49 secret_key.len() >= SECRET_KEY_MIN_LEN
50}
51
52#[derive(Serialize, Deserialize, Clone, Default, Debug)]
126pub struct Credentials {
127 pub access_key: String,
128 pub secret_key: String,
129 pub session_token: String,
130 pub expiration: Option<OffsetDateTime>,
131 pub status: String,
132 pub parent_user: String,
133 pub groups: Option<Vec<String>>,
134 pub claims: Option<HashMap<String, Value>>,
135 pub name: Option<String>,
136 pub description: Option<String>,
137}
138
139impl Credentials {
140 pub fn is_expired(&self) -> bool {
150 if self.expiration.is_none() {
151 return false;
152 }
153
154 self.expiration
155 .as_ref()
156 .map(|e| time::OffsetDateTime::now_utc() > *e)
157 .unwrap_or(false)
158 }
159
160 pub fn is_temp(&self) -> bool {
161 !self.session_token.is_empty() && !self.is_expired()
162 }
163
164 pub fn is_service_account(&self) -> bool {
165 const IAM_POLICY_CLAIM_NAME_SA: &str = "sa-policy";
166 self.claims
167 .as_ref()
168 .map(|x| x.get(IAM_POLICY_CLAIM_NAME_SA).is_some_and(|_| !self.parent_user.is_empty()))
169 .unwrap_or_default()
170 }
171
172 pub fn is_implied_policy(&self) -> bool {
173 if self.is_service_account() {
174 return self
175 .claims
176 .as_ref()
177 .map(|x| x.get(&iam_policy_claim_name_sa()).is_some_and(|v| v == INHERITED_POLICY_TYPE))
178 .unwrap_or_default();
179 }
180
181 false
182 }
183
184 pub fn is_valid(&self) -> bool {
185 if self.status == "off" {
186 return false;
187 }
188
189 self.access_key.len() >= 3 && self.secret_key.len() >= 8 && !self.is_expired()
190 }
191
192 pub fn is_owner(&self) -> bool {
193 false
194 }
195}
196
197pub fn generate_credentials() -> Result<(String, String)> {
198 let ak = utils::gen_access_key(20)?;
199 let sk = utils::gen_secret_key(40)?;
200 Ok((ak, sk))
201}
202
203pub fn get_new_credentials_with_metadata(claims: &HashMap<String, Value>, token_secret: &str) -> Result<Credentials> {
204 let (ak, sk) = generate_credentials()?;
205
206 create_new_credentials_with_metadata(&ak, &sk, claims, token_secret)
207}
208
209pub fn create_new_credentials_with_metadata(
210 ak: &str,
211 sk: &str,
212 claims: &HashMap<String, Value>,
213 token_secret: &str,
214) -> Result<Credentials> {
215 if ak.len() < ACCESS_KEY_MIN_LEN || ak.len() > ACCESS_KEY_MAX_LEN {
216 return Err(IamError::InvalidAccessKeyLength);
217 }
218
219 if sk.len() < SECRET_KEY_MIN_LEN || sk.len() > SECRET_KEY_MAX_LEN {
220 return Err(IamError::InvalidAccessKeyLength);
221 }
222
223 if token_secret.is_empty() {
224 return Ok(Credentials {
225 access_key: ak.to_owned(),
226 secret_key: sk.to_owned(),
227 status: ACCOUNT_OFF.to_owned(),
228 ..Default::default()
229 });
230 }
231
232 let expiration = {
233 if let Some(v) = claims.get("exp") {
234 if let Some(expiry) = v.as_i64() {
235 Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(offset!(+8)))
236 } else {
237 None
238 }
239 } else {
240 None
241 }
242 };
243
244 let token = utils::generate_jwt(&claims, token_secret)?;
245
246 Ok(Credentials {
247 access_key: ak.to_owned(),
248 secret_key: sk.to_owned(),
249 session_token: token,
250 status: ACCOUNT_ON.to_owned(),
251 expiration,
252 ..Default::default()
253 })
254}
255
256pub fn get_claims_from_token_with_secret<T: DeserializeOwned>(token: &str, secret: &str) -> Result<T> {
257 let ms = extract_claims::<T>(token, secret)?;
258 Ok(ms.claims)
260}
261
262pub fn jwt_sign<T: Serialize>(claims: &T, token_secret: &str) -> Result<String> {
263 let token = utils::generate_jwt(claims, token_secret)?;
264 Ok(token)
265}
266
267#[derive(Default)]
268pub struct CredentialsBuilder {
269 session_policy: Option<Policy>,
270 access_key: String,
271 secret_key: String,
272 name: Option<String>,
273 description: Option<String>,
274 expiration: Option<OffsetDateTime>,
275 allow_site_replicator_account: bool,
276 claims: Option<serde_json::Value>,
277 parent_user: String,
278 groups: Option<Vec<String>>,
279}
280
281impl CredentialsBuilder {
282 pub fn new() -> Self {
283 Self::default()
284 }
285
286 pub fn session_policy(mut self, policy: Option<Policy>) -> Self {
287 self.session_policy = policy;
288 self
289 }
290
291 pub fn access_key(mut self, access_key: String) -> Self {
292 self.access_key = access_key;
293 self
294 }
295
296 pub fn secret_key(mut self, secret_key: String) -> Self {
297 self.secret_key = secret_key;
298 self
299 }
300
301 pub fn name(mut self, name: String) -> Self {
302 self.name = Some(name);
303 self
304 }
305
306 pub fn description(mut self, description: String) -> Self {
307 self.description = Some(description);
308 self
309 }
310
311 pub fn expiration(mut self, expiration: Option<OffsetDateTime>) -> Self {
312 self.expiration = expiration;
313 self
314 }
315
316 pub fn allow_site_replicator_account(mut self, allow_site_replicator_account: bool) -> Self {
317 self.allow_site_replicator_account = allow_site_replicator_account;
318 self
319 }
320
321 pub fn claims(mut self, claims: serde_json::Value) -> Self {
322 self.claims = Some(claims);
323 self
324 }
325
326 pub fn parent_user(mut self, parent_user: String) -> Self {
327 self.parent_user = parent_user;
328 self
329 }
330
331 pub fn groups(mut self, groups: Vec<String>) -> Self {
332 self.groups = Some(groups);
333 self
334 }
335
336 pub fn try_build(self) -> Result<Credentials> {
337 self.try_into()
338 }
339}
340
341impl TryFrom<CredentialsBuilder> for Credentials {
342 type Error = Error;
343 fn try_from(mut value: CredentialsBuilder) -> std::result::Result<Self, Self::Error> {
344 if value.parent_user.is_empty() {
345 return Err(IamError::InvalidArgument);
346 }
347
348 if (value.access_key.is_empty() && !value.secret_key.is_empty())
349 || (!value.access_key.is_empty() && value.secret_key.is_empty())
350 {
351 return Err(Error::other("Either ak or sk is empty"));
352 }
353
354 if value.parent_user == value.access_key.as_str() {
355 return Err(IamError::InvalidArgument);
356 }
357
358 if value.access_key == "site-replicator-0" && !value.allow_site_replicator_account {
359 return Err(IamError::InvalidArgument);
360 }
361
362 let mut claim = serde_json::json!({
363 "parent": value.parent_user
364 });
365
366 if let Some(p) = value.session_policy {
367 p.is_valid()?;
368 let policy_buf = serde_json::to_vec(&p).map_err(|_| IamError::InvalidArgument)?;
369 if policy_buf.len() > 4096 {
370 return Err(Error::other("session policy is too large"));
371 }
372 claim["sessionPolicy"] = serde_json::json!(base64_simd::STANDARD.encode_to_string(&policy_buf));
373 claim["sa-policy"] = serde_json::json!("embedded-policy");
374 } else {
375 claim["sa-policy"] = serde_json::json!("inherited-policy");
376 }
377
378 if let Some(Value::Object(obj)) = value.claims {
379 for (key, value) in obj {
380 if claim.get(&key).is_some() {
381 continue;
382 }
383 claim[key] = value;
384 }
385 }
386
387 if value.access_key.is_empty() {
388 value.access_key = utils::gen_access_key(20)?;
389 }
390
391 if value.secret_key.is_empty() {
392 value.access_key = utils::gen_secret_key(40)?;
393 }
394
395 claim["accessKey"] = json!(&value.access_key);
396
397 let mut cred = Credentials {
398 status: "on".into(),
399 parent_user: value.parent_user,
400 groups: value.groups,
401 name: value.name,
402 description: value.description,
403 ..Default::default()
404 };
405
406 if !value.secret_key.is_empty() {
407 let session_token = rustfs_crypto::jwt_encode(value.access_key.as_bytes(), &claim)
408 .map_err(|_| Error::other("session policy is too large"))?;
409 cred.session_token = session_token;
410 } else {
420 }
423
424 cred.expiration = value.expiration;
425 cred.access_key = value.access_key;
426 cred.secret_key = value.secret_key;
427
428 Ok(cred)
429 }
430}
431
432