use std::{collections::HashMap, mem, sync::Arc, time::SystemTime};
use super::{
path_role::RoleEntry,
validation::{create_hmac, verify_cidr_role_secret_id_subset},
AppRoleBackend, AppRoleBackendInner,
};
use crate::{
context::Context,
errors::RvError,
logical::{Auth, Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response},
new_fields, new_fields_internal, new_path, new_path_internal,
storage::StorageEntry,
utils::cidr,
};
impl AppRoleBackend {
pub fn login_path(&self) -> Path {
let approle_backend_ref = Arc::clone(&self.inner);
let path = new_path!({
pattern: r"login$",
fields: {
"role_id": {
field_type: FieldType::Str,
required: true,
description: "Unique identifier of the Role. Required to be supplied when the 'bind_secret_id' constraint is set."
},
"secret_id": {
field_type: FieldType::Str,
required: true,
description: "SecretID belong to the App role"
}
},
operations: [
{op: Operation::Write, handler: approle_backend_ref.login}
],
help: r#"
While the credential 'role_id' is required at all times,
other credentials required depends on the properties App role
to which the 'role_id' belongs to. The 'bind_secret_id'
constraint (enabled by default) on the App role requires the
'secret_id' credential to be presented.
'role_id' is fetched using the 'role/<role_name>/role_id'
endpoint and 'secret_id' is fetched using the 'role/<role_name>/secret_id'
endpoint."#
});
path
}
}
impl AppRoleBackendInner {
pub fn login(&self, _backend: &dyn Backend, req: &mut Request) -> Result<Option<Response>, RvError> {
let role_id = req.get_data_as_str("role_id")?;
let role_id_entry = self.get_role_id(req, &role_id)?;
if role_id_entry.is_none() {
return Err(RvError::ErrResponse("invalid role_id".to_string()));
}
let role_id_entry = role_id_entry.unwrap();
let role_name = role_id_entry.name.clone();
let role_entry: RoleEntry;
{
let lock_entry = self.role_locks.get_lock(&role_name);
let _locked = lock_entry.lock.read()?;
role_entry = self
.get_role(req, &role_id_entry.name)?
.ok_or_else(|| RvError::ErrResponse("invalid role_id".to_string()))?;
}
let mut metadata: HashMap<String, String> = HashMap::new();
let storage = Arc::as_ref(req.storage.as_ref().unwrap());
if role_entry.bind_secret_id {
let secret_id = req.get_data_as_str("secret_id")?;
let secret_id_hmac = create_hmac(&role_entry.hmac_key, &secret_id)?;
let role_name_hmac = create_hmac(&role_entry.hmac_key, &role_entry.name)?;
let entry_index = format!("{}{}/{}", &role_entry.secret_id_prefix, &role_name_hmac, &secret_id_hmac);
let lock_entry = self.secret_id_locks.get_lock(&secret_id_hmac);
let locked = lock_entry.lock.read()?;
let secret_id_entry = self
.get_secret_id_storage_entry(storage, &role_entry.secret_id_prefix, &role_name_hmac, &secret_id_hmac)?
.ok_or(RvError::ErrResponse("invalid secret id".to_string()))?;
let accessor_entry = self.get_secret_id_accessor_entry(
storage,
&secret_id_entry.secret_id_accessor,
&role_entry.secret_id_prefix,
)?;
if accessor_entry.is_none() {
if let Err(err) = storage.delete(&entry_index) {
return Err(RvError::ErrResponse(format!(
"error deleting secret_id {} from storage: {}",
&secret_id_hmac, err
)));
}
return Err(RvError::ErrResponse("invalid secret_id".to_string()));
}
if secret_id_entry.secret_id_num_uses == 0 {
verify_cidr_role_secret_id_subset(&secret_id_entry.cidr_list, &role_entry.secret_id_bound_cidrs)?;
if !secret_id_entry.cidr_list.is_empty() {
let conn = req
.connection
.as_ref()
.ok_or_else(|| RvError::ErrResponse("failed to get connection information".to_string()))?;
if conn.peer_addr.is_empty() {
return Err(RvError::ErrResponse("failed to get connection information".to_string()));
}
let cidr_list_ref: Vec<&str> = secret_id_entry.cidr_list.iter().map(AsRef::as_ref).collect();
if !cidr::ip_belongs_to_cidrs(&conn.peer_addr, &cidr_list_ref)? {
return Err(RvError::ErrResponse(format!(
"source address {} unauthorized through CIDR restrictions on the secret ID",
conn.peer_addr
)));
}
}
} else {
mem::drop(locked);
let _locked = lock_entry.lock.write()?;
let mut secret_id_entry = self
.get_secret_id_storage_entry(
storage,
&role_entry.secret_id_prefix,
&role_name_hmac,
&secret_id_hmac,
)?
.ok_or(RvError::ErrResponse("invalid secret id".to_string()))?;
if secret_id_entry.secret_id_num_uses == 1 {
self.delete_secret_id_accessor_entry(
storage,
&secret_id_entry.secret_id_accessor,
&role_entry.secret_id_prefix,
)?;
storage.delete(&entry_index)?;
} else {
secret_id_entry.secret_id_num_uses -= 1;
secret_id_entry.last_updated_time = SystemTime::now();
let entry = StorageEntry::new(&entry_index, &secret_id_entry)?;
storage.put(&entry)?;
}
verify_cidr_role_secret_id_subset(&secret_id_entry.cidr_list, &role_entry.secret_id_bound_cidrs)?;
if !secret_id_entry.cidr_list.is_empty() {
let conn = req
.connection
.as_ref()
.ok_or_else(|| RvError::ErrResponse("failed to get connection information".to_string()))?;
if conn.peer_addr.is_empty() {
return Err(RvError::ErrResponse("failed to get connection information".to_string()));
}
let cidr_list_ref: Vec<&str> = secret_id_entry.cidr_list.iter().map(AsRef::as_ref).collect();
if !cidr::ip_belongs_to_cidrs(&conn.peer_addr, &cidr_list_ref)? {
return Err(RvError::ErrResponse(format!(
"source address {} unauthorized through CIDR restrictions on the secret ID",
conn.peer_addr
)));
}
}
}
metadata = secret_id_entry.metadata;
}
if !role_entry.secret_id_bound_cidrs.is_empty() {
let conn = req
.connection
.as_ref()
.ok_or_else(|| RvError::ErrResponse("failed to get connection information".to_string()))?;
if conn.peer_addr.is_empty() {
return Err(RvError::ErrResponse("failed to get connection information".to_string()));
}
let bound_cidrs_ref: Vec<&str> = role_entry.secret_id_bound_cidrs.iter().map(AsRef::as_ref).collect();
if !cidr::ip_belongs_to_cidrs(&conn.peer_addr, &bound_cidrs_ref)? {
return Err(RvError::ErrResponse(format!(
"source address {} unauthorized by CIDR restrictions on the secret ID",
conn.peer_addr
)));
}
}
metadata.insert("role_name".to_string(), role_entry.name.clone());
let mut auth = Auth { metadata, ..Default::default() };
auth.internal_data.insert("role_name".to_string(), role_entry.name.clone());
role_entry.populate_token_auth(&mut auth);
let resp = Response { auth: Some(auth), ..Response::default() };
Ok(Some(resp))
}
}