use crate::{Error, LeasedData, Response};
use async_trait::async_trait;
use reqwest::Method;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug, Eq, PartialEq)]
pub struct RootCredentials {
#[serde(default = "default_max_retries")]
pub max_retries: i64,
pub access_key: String,
pub secret_key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iam_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sts_endpoint: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct Lease {
pub lease: String,
pub lease_max: String,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)]
pub struct Role {}
#[derive(Serialize, Debug, Eq, PartialEq, Default)]
pub struct CredentialsRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub role_arn: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Default)]
pub struct Credentials {
pub access_key: String,
pub secret_key: String,
#[serde(default)]
pub security_token: Option<String>,
}
#[async_trait]
pub trait Aws {
async fn configure_root(&self, path: &str, config: &RootCredentials)
-> Result<Response, Error>;
async fn rotate_root(&self, path: &str) -> Result<Response, Error>;
async fn configure_lease(&self, path: &str, lease: &Lease) -> Result<Response, Error>;
async fn read_lease(&self, path: &str) -> Result<Lease, Error>;
async fn create_role(&self, path: &str, role: &Role) -> Result<Response, Error>;
async fn update_role(&self, path: &str, role: &Role) -> Result<Response, Error> {
self.create_role(path, role).await
}
async fn read_role(&self, path: &str, role: &str) -> Result<Role, Error>;
async fn list_roles(&self, path: &str) -> Result<Vec<String>, Error>;
async fn delete_role(&self, path: &str, role: &str) -> Result<Response, Error>;
async fn generate_credentials(
&self,
path: &str,
role: &str,
request: &CredentialsRequest,
) -> Result<LeasedData<Credentials>, Error>;
}
#[async_trait]
impl<T> Aws for T
where
T: crate::Vault + Send + Sync,
{
async fn configure_root(
&self,
path: &str,
config: &RootCredentials,
) -> Result<Response, Error> {
let values = serde_json::to_value(config)?;
let path = format!("{}/config/root", path);
self.post(&path, &values, false).await
}
async fn rotate_root(&self, path: &str) -> Result<Response, Error> {
let path = format!("{}/config/rotate-root", path);
self.read(&path, Method::POST).await
}
async fn configure_lease(&self, path: &str, lease: &Lease) -> Result<Response, Error> {
let values = serde_json::to_value(lease)?;
let path = format!("{}/config/lease", path);
self.post(&path, &values, false).await
}
async fn read_lease(&self, path: &str) -> Result<Lease, Error> {
let path = format!("{}/config/lease", path);
let data: Lease = self.get(&path).await?.data()?;
Ok(data)
}
async fn create_role(&self, _path: &str, _role: &Role) -> Result<Response, Error> {
unimplemented!()
}
async fn read_role(&self, _path: &str, _role: &str) -> Result<Role, Error> {
unimplemented!()
}
async fn list_roles(&self, _path: &str) -> Result<Vec<String>, Error> {
unimplemented!()
}
async fn delete_role(&self, _path: &str, _role: &str) -> Result<Response, Error> {
unimplemented!()
}
async fn generate_credentials(
&self,
path: &str,
role: &str,
request: &CredentialsRequest,
) -> Result<LeasedData<Credentials>, Error> {
let path = format!("{}/creds/{}", path, role);
self.get_with_query(&path, request).await?.leased_data()
}
}
#[allow(dead_code)]
const fn default_max_retries() -> i64 {
-1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sys::mounts::tests::Mount;
#[tokio::test(threaded_scheduler)]
async fn can_configure() {
let client = crate::tests::vault_client();
let path = crate::tests::uuid_prefix("aws");
let engine = crate::sys::mounts::SecretEngine {
path,
r#type: "aws".to_string(),
..Default::default()
};
let mount = Mount::new(&client, &engine).await;
let config = RootCredentials {
max_retries: -1,
access_key: "aaa".to_string(),
secret_key: "aaa".to_string(),
region: None,
iam_endpoint: Some("http://aws_iam:5000".to_string()),
sts_endpoint: Some("http://aws_sts:8000".to_string()),
};
let response = Aws::configure_root(&client, &mount.path, &config)
.await
.unwrap();
assert!(response.ok().unwrap().is_none());
let lease = Lease {
lease: "1h".to_string(),
lease_max: "24h".to_string(),
};
let response = Aws::configure_lease(&client, &mount.path, &lease)
.await
.unwrap();
assert!(response.ok().unwrap().is_none());
let actual_lease = Aws::read_lease(&client, &mount.path).await.unwrap();
assert_eq!(actual_lease.lease, "1h0m0s");
assert_eq!(actual_lease.lease_max, "24h0m0s");
}
}