use ::serde::{Deserialize, Serialize};
use getset::Getters;
use std::collections::HashMap;
use time::OffsetDateTime;
use typed_builder::TypedBuilder;
use uuid::Uuid;
#[cfg(feature = "wrapped")]
pub(crate) type EmptyResponse = Response<(), ()>;
#[cfg(feature = "check_approle")]
pub(crate) type AppRolesResponse = Response<AppRoles, ()>;
pub(crate) type SecretDataResponse = Response<SecretData, ()>;
pub(crate) type AuthResponse = Response<(), Auth>;
pub(crate) type AttResponse<T> = Response<AttProdSecrets<T>, ()>;
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub(crate)")]
pub(crate) struct Response<T, U> {
#[serde(deserialize_with = "serde::to_uuid")]
request_id: Uuid,
#[serde(deserialize_with = "serde::to_uuid")]
lease_id: Uuid,
lease_duration: usize,
renewable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
wrap_info: Option<WrapInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
warnings: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
auth: Option<U>,
}
#[cfg(feature = "check_approle")]
#[derive(Clone, Debug, Deserialize, Getters, Serialize)]
#[getset(get = "pub(crate)")]
pub(crate) struct AppRoles {
keys: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub(crate)")]
pub(crate) struct WrapInfo {
#[cfg_attr(test, builder(setter(into)))]
token: String,
#[cfg_attr(test, builder(setter(into)))]
accessor: String,
ttl: usize,
#[serde(with = "time::serde::rfc3339")]
creation_time: OffsetDateTime,
#[cfg_attr(test, builder(setter(into)))]
creation_path: String,
}
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub(crate)")]
pub(crate) struct Auth {
#[cfg_attr(test, builder(setter(into)))]
client_token: String,
#[cfg_attr(test, builder(setter(into)))]
accessor: String,
policies: Vec<String>,
token_policies: Vec<String>,
lease_duration: usize,
renewable: bool,
#[serde(deserialize_with = "serde::to_uuid")]
entity_id: Uuid,
#[cfg_attr(test, builder(setter(into)))]
token_type: String,
orphan: bool,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub(crate)")]
pub(crate) struct SecretData {
secret_id: Uuid,
secret_id_accessor: Uuid,
secret_id_ttl: usize,
}
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub(crate)")]
pub(crate) struct AttProdSecrets<T> {
data: T,
metadata: Metadata,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(test, derive(Getters, TypedBuilder))]
#[cfg_attr(test, getset(get))]
pub(crate) struct Metadata {
#[serde(with = "time::serde::rfc3339")]
created_time: OffsetDateTime,
#[serde(
deserialize_with = "serde::to_datetime",
serialize_with = "time::serde::rfc3339::serialize"
)]
deletion_time: OffsetDateTime,
destroyed: bool,
version: usize,
}
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub")]
pub(crate) struct Secrets {
#[cfg_attr(test, builder(setter(into)))]
db_pass: String,
#[cfg_attr(test, builder(setter(into)))]
api_key: String,
}
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub")]
pub(crate) struct MuxmSecrets {
timeout: String,
}
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub")]
pub(crate) struct MuxwSecrets {
commands: Vec<Command>,
overrides: HashMap<String, Vec<Command>>,
}
#[derive(Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
#[cfg_attr(test, derive(TypedBuilder))]
#[getset(get = "pub")]
pub(crate) struct Command {
name: String,
cmd: String,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, TypedBuilder)]
pub(crate) struct Login {
role_id: Uuid,
secret_id: Uuid,
}
pub(crate) mod serde {
use crate::constants::{DATETIME_EXPECTING, PARSE_DATETIME, PARSE_UUID, UUID_EXPECTING};
use serde::{
de::{self, Visitor},
Deserializer,
};
use std::fmt;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use uuid::Uuid;
#[allow(single_use_lifetimes)]
pub(crate) fn to_uuid<'de, D>(deser: D) -> Result<Uuid, D::Error>
where
D: Deserializer<'de>,
{
struct AsUuid;
impl<'de> Visitor<'de> for AsUuid {
type Value = Uuid;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(UUID_EXPECTING)
}
fn visit_str<E>(self, value: &str) -> Result<Uuid, E>
where
E: de::Error,
{
Ok(if value.is_empty() {
Uuid::nil()
} else {
Uuid::parse_str(value).map_err(|_e| de::Error::custom(PARSE_UUID))?
})
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_str(&value)
}
}
deser.deserialize_any(AsUuid)
}
#[allow(single_use_lifetimes)]
pub(crate) fn to_datetime<'de, D>(deser: D) -> Result<OffsetDateTime, D::Error>
where
D: Deserializer<'de>,
{
struct AsDateTimeUtc;
impl<'de> Visitor<'de> for AsDateTimeUtc {
type Value = OffsetDateTime;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(DATETIME_EXPECTING)
}
fn visit_str<E>(self, value: &str) -> Result<OffsetDateTime, E>
where
E: de::Error,
{
Ok(if value.is_empty() {
OffsetDateTime::now_utc()
} else {
OffsetDateTime::parse(value, &Rfc3339)
.map_err(|_e| de::Error::custom(PARSE_DATETIME))?
})
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_str(&value)
}
}
deser.deserialize_any(AsDateTimeUtc)
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "wrapped")]
use super::EmptyResponse;
use super::{AttProdSecrets, Auth, Login, Metadata, SecretData, Secrets, WrapInfo};
use anyhow::Result;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use time::OffsetDateTime;
use uuid::Uuid;
const LOGIN_JSON: &'static str = r#"{"role_id":"00000000-0000-0000-0000-000000000000","secret_id":"00000000-0000-0000-0000-000000000000"}"#;
const SECRETS_JSON: &'static str = r#"{"db_pass":"password","api_key":"api_key"}"#;
const METADATA_JSON: &'static str = r#"{"created_time":"2001-09-09T01:46:40Z","deletion_time":"2001-09-09T01:46:40Z","destroyed":false,"version":1}"#;
const METADATA_NO_DEL_JSON: &'static str = r#"{"created_time":"2001-09-09T01:46:40Z","deletion_time":"","destroyed":false,"version":1}"#;
const ATT_PROD_SECRETS_JSON: &'static str = r#"{"data":{"db_pass":"password","api_key":"api_key"},"metadata":{"created_time":"2001-09-09T01:46:40Z","deletion_time":"2001-09-09T01:46:40Z","destroyed":false,"version":1}}"#;
const SECRET_DATA_JSON: &'static str = r#"{"secret_id":"00000000-0000-0000-0000-000000000000","secret_id_accessor":"00000000-0000-0000-0000-000000000000","secret_id_ttl":10}"#;
const AUTH_JSON: &'static str = r#"{"client_token":"token","accessor":"accessor","policies":[],"token_policies":[],"lease_duration":10,"renewable":true,"entity_id":"00000000-0000-0000-0000-000000000000","token_type":"token_type","orphan":false}"#;
const WRAP_INFO_JSON: &'static str = r#"{"token":"token","accessor":"accessor","ttl":10,"creation_time":"2001-09-09T01:46:40Z","creation_path":"creation_path"}"#;
#[cfg(feature = "wrapped")]
const EMPTY_RESPONSE_JSON: &'static str = r#"{"request_id":"00000000-0000-0000-0000-000000000000","lease_id":"00000000-0000-0000-0000-000000000000","lease_duration":10,"renewable":true}"#;
#[cfg(feature = "wrapped")]
const EMPTY_RESPONSE_NO_LEASE_JSON: &'static str = r#"{"request_id":"00000000-0000-0000-0000-000000000000","lease_id":"","lease_duration":10,"renewable":true}"#;
lazy_static! {
static ref TEST_DATE_TIME: OffsetDateTime =
OffsetDateTime::from_unix_timestamp(1_000_000_000).expect("");
static ref LOGIN: Login = Login::builder()
.role_id(Uuid::nil())
.secret_id(Uuid::nil())
.build();
static ref SECRETS: Secrets = Secrets::builder()
.db_pass("password")
.api_key("api_key")
.build();
static ref METADATA: Metadata = Metadata::builder()
.created_time(*TEST_DATE_TIME)
.deletion_time(*TEST_DATE_TIME)
.destroyed(false)
.version(1)
.build();
static ref ATT_PROD_SECRETS: AttProdSecrets<Secrets> = AttProdSecrets::builder()
.data((*SECRETS).clone())
.metadata(*METADATA)
.build();
static ref SECRET_DATA: SecretData = SecretData::builder()
.secret_id(Uuid::nil())
.secret_id_accessor(Uuid::nil())
.secret_id_ttl(10)
.build();
static ref AUTH: Auth = Auth::builder()
.client_token("token")
.accessor("accessor")
.policies(vec![])
.token_policies(vec![])
.lease_duration(10)
.renewable(true)
.entity_id(Uuid::nil())
.token_type("token_type")
.orphan(false)
.build();
static ref WRAP_INFO: WrapInfo = WrapInfo::builder()
.token("token")
.accessor("accessor")
.ttl(10)
.creation_time(*TEST_DATE_TIME)
.creation_path("creation_path")
.build();
}
#[cfg(feature = "wrapped")]
lazy_static! {
static ref EMPTY_RESPONSE: EmptyResponse = EmptyResponse::builder()
.request_id(Uuid::nil())
.lease_id(Uuid::nil())
.lease_duration(10)
.renewable(true)
.data(None)
.wrap_info(None)
.warnings(None)
.auth(None)
.build();
}
#[allow(single_use_lifetimes)]
fn base_serde<'de, T>(json: &'static str, obj: T) -> Result<()>
where
T: Serialize + Deserialize<'de> + PartialEq + Debug,
{
let output = serde_json::to_string(&obj)?;
assert_eq!(output, json);
let sut = serde_json::from_str::<T>(json)?;
assert_eq!(sut, obj);
Ok(())
}
#[test]
fn login_serde() -> Result<()> {
base_serde(LOGIN_JSON, *LOGIN)
}
#[test]
fn secrets_serde() -> Result<()> {
base_serde(SECRETS_JSON, (*SECRETS).clone())
}
#[test]
fn metadata_serde() -> Result<()> {
base_serde(METADATA_JSON, *METADATA)
}
#[test]
fn att_prod_secrets_serde() -> Result<()> {
base_serde(ATT_PROD_SECRETS_JSON, (*ATT_PROD_SECRETS).clone())
}
#[test]
fn secret_data_serde() -> Result<()> {
base_serde(SECRET_DATA_JSON, (*SECRET_DATA).clone())
}
#[test]
fn auth_serde() -> Result<()> {
base_serde(AUTH_JSON, (*AUTH).clone())
}
#[test]
fn wrap_info_serde() -> Result<()> {
base_serde(WRAP_INFO_JSON, (*WRAP_INFO).clone())
}
#[cfg(feature = "wrapped")]
#[test]
fn empty_response_serde() -> Result<()> {
base_serde(EMPTY_RESPONSE_JSON, (*EMPTY_RESPONSE).clone())
}
#[test]
fn metadata_no_del() -> Result<()> {
let sut = serde_json::from_str::<Metadata>(METADATA_NO_DEL_JSON)?;
assert_eq!(*sut.created_time(), *TEST_DATE_TIME);
assert!(!sut.destroyed());
assert_eq!(*sut.version(), 1);
Ok(())
}
#[test]
#[cfg(feature = "wrapped")]
fn empty_response_no_lease() -> Result<()> {
let sut = serde_json::from_str::<EmptyResponse>(EMPTY_RESPONSE_NO_LEASE_JSON)?;
assert_eq!(sut, *EMPTY_RESPONSE);
Ok(())
}
}