aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbenj <benj@rse8.com>2023-06-19 17:18:21 -0700
committerbenj <benj@rse8.com>2023-06-19 17:18:21 -0700
commitab6d5cefbea1e8ddf41f385dd85918f651958287 (patch)
treeac3a6b45b1a0e6a833a627307d07e94a95ba3c23
parent3406b370fe290559ff2445097a380d6f48d0f9af (diff)
downloadsecdiam-ab6d5cefbea1e8ddf41f385dd85918f651958287.tar
secdiam-ab6d5cefbea1e8ddf41f385dd85918f651958287.tar.gz
secdiam-ab6d5cefbea1e8ddf41f385dd85918f651958287.tar.bz2
secdiam-ab6d5cefbea1e8ddf41f385dd85918f651958287.tar.lz
secdiam-ab6d5cefbea1e8ddf41f385dd85918f651958287.tar.xz
secdiam-ab6d5cefbea1e8ddf41f385dd85918f651958287.tar.zst
secdiam-ab6d5cefbea1e8ddf41f385dd85918f651958287.zip
hack to allow impersonator to impersonate target
-rw-r--r--Cargo.lock69
-rw-r--r--crates/iam/Cargo.toml4
-rw-r--r--crates/iam/src/api.rs6
-rw-r--r--crates/iam/src/main.rs24
-rw-r--r--crates/secd/src/auth/n.rs91
-rw-r--r--crates/secd/src/auth/z/mod.rs3
-rw-r--r--crates/secd/src/client/store/mod.rs53
-rw-r--r--crates/secd/src/client/store/sql_db.rs70
-rw-r--r--crates/secd/src/lib.rs85
-rw-r--r--crates/secd/store/pg/migrations/20221222002434_bootstrap.sql33
-rw-r--r--crates/secd/store/pg/sql/find_impersonator.sql10
-rw-r--r--crates/secd/store/pg/sql/write_impersonator.sql11
-rw-r--r--crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql21
-rw-r--r--crates/secd/store/sqlite/sql/find_credential.sql2
-rw-r--r--crates/secd/store/sqlite/sql/find_identity.sql2
-rw-r--r--crates/secd/store/sqlite/sql/find_impersonator.sql10
-rw-r--r--crates/secd/store/sqlite/sql/find_session.sql11
-rw-r--r--crates/secd/store/sqlite/sql/write_credential.sql4
-rw-r--r--crates/secd/store/sqlite/sql/write_impersonator.sql11
-rw-r--r--crates/secd/store/sqlite/sql/write_session.sql11
-rw-r--r--justfile14
21 files changed, 414 insertions, 131 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c9c6f8a..a23fa57 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1249,6 +1249,8 @@ dependencies = [
"tiny_http",
"tokio",
"toml",
+ "tracing",
+ "tracing-subscriber",
"url",
"urlencoding",
"uuid",
@@ -1562,6 +1564,16 @@ dependencies = [
]
[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1664,6 +1676,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2330,6 +2348,15 @@ dependencies = [
]
[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
name = "signal-hook"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2578,6 +2605,16 @@ dependencies = [
]
[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
name = "time"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2836,6 +2873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
dependencies = [
"once_cell",
+ "valuable",
]
[[package]]
@@ -2849,6 +2887,31 @@ dependencies = [
]
[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+dependencies = [
+ "nu-ansi-term",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2943,6 +3006,12 @@ dependencies = [
]
[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/iam/Cargo.toml b/crates/iam/Cargo.toml
index 2ee6fc6..25af19e 100644
--- a/crates/iam/Cargo.toml
+++ b/crates/iam/Cargo.toml
@@ -23,7 +23,9 @@ time = { version = "0.3", features = [ "serde" ] }
tiny_http = "0.12"
tokio = { version = "1.23.0", features = ["full"] }
toml = "0.5.9"
+tracing = "0.1.37"
+tracing-subscriber = "0.3.17"
thiserror = "1.0"
url = "2.3.1"
urlencoding = "2.1.2"
-uuid = { version = "1.2", features = ["v4", "serde"]} \ No newline at end of file
+uuid = { version = "1.2", features = ["v4", "serde"]}
diff --git a/crates/iam/src/api.rs b/crates/iam/src/api.rs
index c662e0c..7865a75 100644
--- a/crates/iam/src/api.rs
+++ b/crates/iam/src/api.rs
@@ -217,6 +217,12 @@ pub enum CreateObject {
#[arg(long, short)]
identity_id: Option<Uuid>,
},
+ Impersonator {
+ /// The identity which will be the source impersonator.
+ impersonator_id: Uuid,
+ /// The identity id which will be the target for impersonation, and for whom a credential will be created.
+ target_id: Uuid,
+ },
Validation {
/// Method by which the validation will occur
#[command(subcommand)]
diff --git a/crates/iam/src/main.rs b/crates/iam/src/main.rs
index 28f4e4c..41e63be 100644
--- a/crates/iam/src/main.rs
+++ b/crates/iam/src/main.rs
@@ -11,7 +11,6 @@ use api::{
use clap::Parser;
use command::dev_oauth2_listen;
-use env_logger::Env;
use secd::{CredentialType, Secd};
use time::OffsetDateTime;
use util::Result;
@@ -24,7 +23,7 @@ const ISSUE_TRACKER_LOC: &str = "https://www.github.com/secdiam/iam";
#[tokio::main]
async fn main() {
- env_logger::init_from_env(Env::default().default_filter_or("debug"));
+ tracing_subscriber::fmt().init();
match exec().await {
Ok(Some(s)) => println!("{}", s),
Err(e) => {
@@ -116,6 +115,13 @@ async fn create(secd: &Secd, cmd: CreateObject) -> Result<Option<String>> {
let credential = secd.create_credential(t, identity_id, expires_at).await?;
Some(serde_json::ser::to_string_pretty(&credential)?.to_string())
}
+ CreateObject::Impersonator {
+ impersonator_id,
+ target_id,
+ } => {
+ let credential = secd.impersonate(&impersonator_id, &target_id).await?;
+ Some(serde_json::to_string(&credential)?.to_string())
+ }
CreateObject::Validation {
method,
identity_id,
@@ -167,9 +173,11 @@ async fn get(secd: &Secd, cmd: GetObject) -> Result<Option<String>> {
key: username,
value: passphrase,
},
- ValidateObject::Session { token } => {
- CredentialType::session_from_str(&token).expect("failed to build session")
- }
+ ValidateObject::Session { token } => CredentialType::session_from_str(&token)
+ .expect(
+ "failed to 23
+build session",
+ ),
});
Some(
@@ -204,21 +212,21 @@ async fn update(secd: &Secd, cmd: UpdateObject) -> Result<Option<String>> {
async fn validate(secd: &Secd, cmd: ValidateObject) -> Result<Option<String>> {
let credential = match cmd {
ValidateObject::ApiToken { token } => {
- secd.validate_credential(CredentialType::api_token_from_str(&token)?)
+ secd.validate_credential(&CredentialType::api_token_from_str(&token)?)
.await?
}
ValidateObject::Passphrase {
username,
passphrase,
} => {
- secd.validate_credential(CredentialType::Passphrase {
+ secd.validate_credential(&CredentialType::Passphrase {
key: username,
value: passphrase,
})
.await?
}
ValidateObject::Session { token } => {
- secd.validate_credential(CredentialType::session_from_str(&token)?)
+ secd.validate_credential(&CredentialType::session_from_str(&token)?)
.await?
}
};
diff --git a/crates/secd/src/auth/n.rs b/crates/secd/src/auth/n.rs
index dde6e7d..12a5411 100644
--- a/crates/secd/src/auth/n.rs
+++ b/crates/secd/src/auth/n.rs
@@ -5,12 +5,13 @@ use crate::{
DEFAULT_SIGNUP_EMAIL,
},
store::{
- AddressLens, AddressValidationLens, CredentialLens, IdentityLens, Storable, StoreError,
+ AddressLens, AddressValidationLens, CredentialLens, IdentityLens, ImpersonatorLens,
+ Storable, StoreError,
},
},
util::{self, ErrorContext},
Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod,
- Credential, CredentialId, CredentialType, Identity, IdentityId, Secd, SecdError,
+ Credential, CredentialId, CredentialType, Identity, IdentityId, Impersonator, Secd, SecdError,
ADDRESSS_VALIDATION_CODE_SIZE, ADDRESS_VALIDATION_ALLOWS_ATTEMPTS,
ADDRESS_VALIDATION_IDENTITY_SURJECTION, EMAIL_VALIDATION_DURATION,
};
@@ -19,6 +20,7 @@ use log::warn;
use rand::Rng;
use std::str::FromStr;
use time::{Duration, OffsetDateTime};
+use tokio::join;
use uuid::Uuid;
impl Secd {
@@ -363,13 +365,13 @@ impl Secd {
Ok(identity)
}
- pub async fn validate_credential(&self, t: CredentialType) -> Result<Credential, SecdError> {
+ pub async fn validate_credential(&self, t: &CredentialType) -> Result<Credential, SecdError> {
let mut retrieved = Credential::find(
self.store.clone(),
&CredentialLens {
id: None,
identity_id: None,
- t: Some(&t),
+ t: Some(t),
},
)
.await?
@@ -396,7 +398,7 @@ impl Secd {
retrieved.hash_compare(&t, &self.crypter)?;
// Return the initially provided plaintext credential since it's valid
- retrieved.t = t;
+ retrieved.t = t.clone();
Ok(retrieved)
}
@@ -503,4 +505,83 @@ impl Secd {
credential.write(self.store.clone()).await?;
Ok(())
}
+
+ pub async fn impersonate(
+ &self,
+ impersonator_id: &IdentityId,
+ target_id: &IdentityId,
+ ) -> Result<Credential, SecdError> {
+ let impersonator_lens = IdentityLens {
+ id: Some(&impersonator_id),
+ address_type: None,
+ validated_address: None,
+ };
+ let target_lens = IdentityLens {
+ id: Some(&target_id),
+ address_type: None,
+ validated_address: None,
+ };
+ let (i, t) = join!(
+ Identity::find(self.store.clone(), &impersonator_lens,),
+ Identity::find(self.store.clone(), &target_lens,)
+ );
+
+ let (i, t) = (
+ i.ctx("failed to retrieve impersonator identity")?,
+ t.ctx("failed to retrieve target identity")?,
+ );
+ if i.is_empty() || t.is_empty() {
+ return Err(SecdError::IdentityNotFound)
+ .ctx("failed to retrieve impersonator or target identity for impersonation");
+ }
+
+ let existing_impersonation = Impersonator::find(
+ self.store.clone(),
+ &ImpersonatorLens {
+ impersonator_id: Some(impersonator_id),
+ target_id: Some(target_id),
+ },
+ )
+ .await
+ .ctx("failed to find existing impersonation")?;
+
+ // TODO: We could expire the session chain, but I think we want to handle this more intelligently.
+ // For now, just revoke the credential manually...pita I know...
+ if !existing_impersonation.is_empty() {
+ return Err(SecdError::ImpersonatorAlreadyExists)
+ .ctx("Target already being impersonated by the provided impersonator identity");
+ }
+
+ let new_identity = self
+ .create_credential(
+ Credential::new_session(*target_id)?.t,
+ Some(*target_id),
+ OffsetDateTime::now_utc().checked_add(Duration::minutes(30)),
+ )
+ .await
+ .ctx("failed to create new credential for target identity")?;
+
+ let new_session = new_identity
+ .new_credentials
+ .iter()
+ .next()
+ .ok_or(SecdError::InvalidCredential)
+ .ctx("failed to retrieve new session from newly created target credential")?
+ .clone();
+
+ Impersonator {
+ impersonator: i
+ .into_iter()
+ .next()
+ .ok_or(SecdError::IdentityNotFound)
+ .ctx("failed to find impersonator identity")?,
+ target: new_identity,
+ created_at: OffsetDateTime::now_utc(),
+ }
+ .write(self.store.clone())
+ .await
+ .ctx("failed to write new impersonator")?;
+
+ Ok(new_session)
+ }
}
diff --git a/crates/secd/src/auth/z/mod.rs b/crates/secd/src/auth/z/mod.rs
index d64f674..bde319a 100644
--- a/crates/secd/src/auth/z/mod.rs
+++ b/crates/secd/src/auth/z/mod.rs
@@ -49,9 +49,6 @@ impl Authorization for Secd {
.map(|e| Uuid::parse_str(e).unwrap())
.collect())
}
- async fn check_list_subjects(&self) -> Result<Vec<i32>, SecdError> {
- unimplemented!()
- }
async fn write(&self, ts: &[Relationship]) -> Result<(), SecdError> {
let spice = self
.spice
diff --git a/crates/secd/src/client/store/mod.rs b/crates/secd/src/client/store/mod.rs
index 6c42dba..f08aa41 100644
--- a/crates/secd/src/client/store/mod.rs
+++ b/crates/secd/src/client/store/mod.rs
@@ -1,4 +1,4 @@
-pub(crate) mod sql_db;
+pub mod sql_db;
use async_trait::async_trait;
use sqlx::{Postgres, Sqlite};
@@ -7,7 +7,7 @@ use uuid::Uuid;
use crate::{
Address, AddressType, AddressValidation, Credential, CredentialId, CredentialType, Identity,
- IdentityId,
+ IdentityId, Impersonator,
};
use self::sql_db::SqlClient;
@@ -19,6 +19,7 @@ pub enum StoreError {
ParseError(#[from] strum::ParseError),
StoreValueCannotBeParsedInvariant,
IdempotentCheckAlreadyExists,
+ ExpectedEntity,
}
#[async_trait]
@@ -32,7 +33,7 @@ pub enum StoreType {
}
#[async_trait]
-pub(crate) trait Storable<'a> {
+pub trait Storable<'a> {
type Item;
type Lens;
@@ -43,33 +44,39 @@ pub(crate) trait Storable<'a> {
) -> Result<Vec<Self::Item>, StoreError>;
}
-pub(crate) trait Lens {}
+pub trait Lens {}
-pub(crate) struct AddressLens<'a> {
+pub struct AddressLens<'a> {
pub id: Option<&'a Uuid>,
pub t: Option<&'a AddressType>,
}
impl<'a> Lens for AddressLens<'a> {}
-pub(crate) struct AddressValidationLens<'a> {
+pub struct AddressValidationLens<'a> {
pub id: Option<&'a Uuid>,
}
impl<'a> Lens for AddressValidationLens<'a> {}
-pub(crate) struct IdentityLens<'a> {
+pub struct IdentityLens<'a> {
pub id: Option<&'a Uuid>,
pub address_type: Option<&'a AddressType>,
pub validated_address: Option<bool>,
}
impl<'a> Lens for IdentityLens<'a> {}
-pub(crate) struct CredentialLens<'a> {
+pub struct CredentialLens<'a> {
pub id: Option<CredentialId>,
pub identity_id: Option<IdentityId>,
pub t: Option<&'a CredentialType>,
}
impl<'a> Lens for CredentialLens<'a> {}
+pub struct ImpersonatorLens<'a> {
+ pub impersonator_id: Option<&'a IdentityId>,
+ pub target_id: Option<&'a IdentityId>,
+}
+impl<'a> Lens for ImpersonatorLens<'a> {}
+
#[async_trait]
impl<'a> Storable<'a> for Address {
type Item = Address;
@@ -179,3 +186,33 @@ impl<'a> Storable<'a> for Credential {
})
}
}
+
+#[async_trait]
+impl<'a> Storable<'a> for Impersonator {
+ type Item = Impersonator;
+ type Lens = ImpersonatorLens<'a>;
+
+ async fn write(&self, store: Arc<dyn Store>) -> Result<(), StoreError> {
+ match store.get_type() {
+ StoreType::Postgres { c } => c.write_impersonator(self).await?,
+ StoreType::Sqlite { c } => c.write_impersonator(self).await?,
+ }
+ Ok(())
+ }
+
+ async fn find(
+ store: Arc<dyn Store>,
+ lens: &'a Self::Lens,
+ ) -> Result<Vec<Self::Item>, StoreError> {
+ Ok(match store.get_type() {
+ StoreType::Postgres { c } => {
+ c.find_impersonator(lens.impersonator_id, lens.target_id)
+ .await?
+ }
+ StoreType::Sqlite { c } => {
+ c.find_impersonator(lens.impersonator_id, lens.target_id)
+ .await?
+ }
+ })
+ }
+}
diff --git a/crates/secd/src/client/store/sql_db.rs b/crates/secd/src/client/store/sql_db.rs
index 7b3a68e..5777704 100644
--- a/crates/secd/src/client/store/sql_db.rs
+++ b/crates/secd/src/client/store/sql_db.rs
@@ -1,4 +1,5 @@
use super::{Store, StoreError, StoreType};
+use crate::Impersonator;
use crate::{
util::ErrorContext, Address, AddressType, AddressValidation, AddressValidationMethod,
Credential, CredentialId, CredentialType, Identity, IdentityId,
@@ -26,6 +27,8 @@ const WRITE_CREDENTIAL: &str = "write_credential";
const FIND_CREDENTIAL: &str = "find_credential";
const WRITE_IDENTITY: &str = "write_identity";
const FIND_IDENTITY: &str = "find_identity";
+const WRITE_IMPERSONATOR: &str = "write_impersonator";
+const FIND_IMPERSONATOR: &str = "find_impersonator";
const ERR_MSG_MIGRATION_FAILED: &str = "Failed to apply secd migrations to a sql db. File a bug at https://www.github.com/branchcontrol/secdiam";
@@ -64,6 +67,14 @@ lazy_static! {
FIND_CREDENTIAL,
include_str!("../../../store/sqlite/sql/find_credential.sql"),
),
+ (
+ WRITE_IMPERSONATOR,
+ include_str!("../../../store/sqlite/sql/write_impersonator.sql"),
+ ),
+ (
+ FIND_IMPERSONATOR,
+ include_str!("../../../store/sqlite/sql/find_impersonator.sql"),
+ ),
]
.iter()
.cloned()
@@ -102,6 +113,14 @@ lazy_static! {
FIND_CREDENTIAL,
include_str!("../../../store/pg/sql/find_credential.sql"),
),
+ (
+ WRITE_IMPERSONATOR,
+ include_str!("../../../store/pg/sql/write_impersonator.sql"),
+ ),
+ (
+ FIND_IMPERSONATOR,
+ include_str!("../../../store/pg/sql/find_impersonator.sql"),
+ ),
]
.iter()
.cloned()
@@ -525,6 +544,57 @@ where
Ok(res)
}
+
+ pub async fn write_impersonator(&self, i: &Impersonator) -> Result<(), StoreError> {
+ let sqls = get_sqls(&self.sqls_root, WRITE_IMPERSONATOR);
+ sqlx::query(&sqls[0])
+ .bind(i.impersonator.id)
+ .bind(i.target.id)
+ .bind(i.target.new_credentials.get(0).map(|e| &e.id))
+ .bind(i.created_at)
+ .fetch_all(&self.pool)
+ .await
+ .extend_err()?;
+ Ok(())
+ }
+ pub async fn find_impersonator(
+ &self,
+ impersonator_id: Option<&Uuid>,
+ target_id: Option<&Uuid>,
+ ) -> Result<Vec<Impersonator>, StoreError> {
+ let sqls = get_sqls(&self.sqls_root, FIND_IMPERSONATOR);
+ let rs = sqlx::query_as::<_, (Uuid, Uuid, OffsetDateTime)>(&sqls[0])
+ .bind(impersonator_id)
+ .bind(target_id)
+ .bind(OffsetDateTime::now_utc())
+ .fetch_all(&self.pool)
+ .await
+ .extend_err()?;
+
+ let mut res = vec![];
+ for (impersonator_id, target_id, created_at) in rs.into_iter() {
+ let impersonator = self
+ .find_identity(Some(&impersonator_id), None, None)
+ .await?
+ .into_iter()
+ .next()
+ .ok_or(StoreError::ExpectedEntity)?;
+ let target = self
+ .find_identity(Some(&target_id), None, None)
+ .await?
+ .into_iter()
+ .next()
+ .ok_or(StoreError::ExpectedEntity)?;
+
+ res.push(Impersonator {
+ impersonator,
+ target,
+ created_at,
+ })
+ }
+
+ Ok(res)
+ }
}
fn get_sqls(root: &str, file: &str) -> Vec<String> {
diff --git a/crates/secd/src/lib.rs b/crates/secd/src/lib.rs
index c84ce44..7fa1755 100644
--- a/crates/secd/src/lib.rs
+++ b/crates/secd/src/lib.rs
@@ -41,9 +41,7 @@ pub type AddressId = Uuid;
pub type AddressValidationId = Uuid;
pub type CredentialId = Uuid;
pub type IdentityId = Uuid;
-pub type MotifId = Uuid;
pub type PhoneNumber = String;
-pub type RefId = Uuid;
#[derive(Debug, derive_more::Display, thiserror::Error)]
pub enum SecdError {
@@ -60,10 +58,15 @@ pub enum SecdError {
CrypterError(#[from] CrypterError),
+ CfgMissingSpiceSecret,
+ CfgMissingSpiceServer,
+
TooManyIdentities,
IdentityNotFound,
IdentityAlreadyExists,
+ ImpersonatorAlreadyExists,
+
EmailMessengerError(#[from] EmailMessengerError),
InvalidEmaillAddress(#[from] email_address::Error),
@@ -103,43 +106,40 @@ struct Cfg {
email_signup_message_asset_loc: Option<String>,
email_signin_message: Option<String>,
email_signup_message: Option<String>,
- spice_secret: String,
- spice_server: String,
+ spice_secret: Option<String>,
+ spice_server: Option<String>,
}
#[async_trait]
pub trait Authentication {
- async fn validate_address(
- &self,
- address_type: AddressType,
- identity_id: Option<IdentityId>,
- ) -> Result<AddressValidation, SecdError>;
-
- async fn complete_address_validation(
- &self,
- validation_id: &AddressValidationId,
- plaintext_token: Option<String>,
- plaintext_code: Option<String>,
- ) -> Result<AddressValidation, SecdError>;
-
+ async fn check_credential(&self, t: &CredentialType) -> Result<Credential, SecdError>;
async fn create_credential(
&self,
t: &CredentialType,
identity_id: Option<IdentityId>,
- ) -> Result<IdentityId, SecdError>;
- // async fn update_credential(&self, t: &CredentialType) -> Result<(), SecdError>;
- async fn reset_credential(
+ expires_at: Option<OffsetDateTime>,
+ ) -> Result<Identity, SecdError>;
+ async fn create_identity(
&self,
+ i: &Identity,
t: &CredentialType,
- address: &AddressType,
+ md: Option<String>,
+ ) -> Result<Identity, SecdError>;
+ async fn impersonate(
+ &self,
+ impersonator: &Identity,
+ target: &Identity,
) -> Result<Credential, SecdError>;
- async fn validate_credential(&self, t: &CredentialType) -> Result<Credential, SecdError>;
-
- // async fn expire_session_chain(&self, t: &SessionToken) -> Result<(), SecdError>;
- // async fn expire_sessions(&self, i: &IdentityId) -> Result<(), SecdError>;
-
+ async fn revoke_credential(&self, credential_id: &CredentialId) -> Result<Identity, SecdError>;
+ async fn send_address_validation(&self, t: AddressType)
+ -> Result<AddressValidation, SecdError>;
+ async fn validate_address(
+ &self,
+ v_id: &AddressValidationId,
+ plaintext_token: Option<String>,
+ plaintext_code: Option<String>,
+ ) -> Result<AddressValidation, SecdError>;
// async fn get_identity(&self, t: &SessionToken) -> Result<Identity, SecdError>;
- // async fn get_session(&self, t: &SessionToken) -> Result<Session, SecdError>;
}
#[async_trait]
@@ -151,7 +151,6 @@ pub trait Authorization {
subj: &Subject,
relation: &Relation,
) -> Result<Vec<Uuid>, SecdError>;
- async fn check_list_subjects(&self) -> Result<Vec<i32>, SecdError>;
async fn write(&self, relationships: &[Relationship]) -> Result<(), SecdError>;
}
@@ -218,7 +217,7 @@ pub enum AddressType {
}
#[serde_as]
-#[derive(Debug, Serialize)]
+#[derive(Clone, Debug, Serialize)]
pub struct Credential {
pub id: CredentialId,
pub identity_id: IdentityId,
@@ -263,6 +262,15 @@ pub struct Identity {
pub deleted_at: Option<OffsetDateTime>,
}
+#[serde_with::skip_serializing_none]
+#[derive(Debug, Serialize)]
+pub struct Impersonator {
+ pub impersonator: Identity,
+ pub target: Identity,
+ #[serde(with = "time::serde::timestamp")]
+ pub created_at: OffsetDateTime,
+}
+
impl Cfg {
fn resolve(&mut self) -> Result<(), SecdError> {
if let Some(path) = &self.email_signin_message_asset_loc {
@@ -313,8 +321,8 @@ impl Secd {
CRYPTER_SECRET_KEY_DEFAULT.to_string()
});
- info!("starting client with auth_store: {:?}", auth_store);
- info!("starting client with email_messenger: {:?}", auth_store);
+ info!("init with auth_store: {:?}", auth_store);
+ info!("init with email_messenger: {:?}", email_messenger);
let store = match auth_store {
AuthStore::Sqlite { conn } => {
@@ -340,7 +348,7 @@ impl Secd {
.connect(&conn)
.await
.map_err(|e| {
- SecdError::StoreInitFailure(format!("failed to init sqlite: {}", e))
+ SecdError::StoreInitFailure(format!("failed to init postgres: {}", e))
})?,
)
.await
@@ -366,8 +374,17 @@ impl Secd {
let spice = match z_schema {
Some(schema) => {
- let c: Arc<Spice> =
- Arc::new(Spice::new(cfg.spice_secret.clone(), cfg.spice_server.clone()).await);
+ let c: Arc<Spice> = Arc::new(
+ Spice::new(
+ cfg.spice_secret
+ .clone()
+ .ok_or(SecdError::CfgMissingSpiceSecret)?,
+ cfg.spice_server
+ .clone()
+ .ok_or(SecdError::CfgMissingSpiceServer)?,
+ )
+ .await,
+ );
c.write_schema(schema)
.await
.unwrap_or_else(|_| panic!("{}", "failed to write authorization schema"));
diff --git a/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql b/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql
index 0fd423e..8f0a9c0 100644
--- a/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql
+++ b/crates/secd/store/pg/migrations/20221222002434_bootstrap.sql
@@ -66,16 +66,6 @@ create table if not exists secd.address_validation (
, unique(address_validation_public_id)
);
-create table if not exists secd.session (
- session_id bigserial primary key
- , identity_id bigint not null references secd.identity(identity_id)
- , token_hash bytea not null
- , created_at timestamptz not null
- , expired_at timestamptz not null
- , revoked_at timestamptz
- , unique(token_hash)
-);
-
create table if not exists secd.message (
message_id bigserial primary key
, address_id bigint not null references secd.address(address_id)
@@ -87,22 +77,9 @@ create table if not exists secd.message (
, sent_at timestamptz
);
-create table if not exists secd.namespace_config (
- namespace text not null
- , serialized_config text not null
- , created_at xid8 not null
- , deleted_at xid8
- -- TODO: indexes and stuff
-);
-
-create table if not exists secd.relation_tuple (
- namespace text not null
- , object_id text not null
- , relation text not null
- , userset_namespace text not null
- , userset_object_id text not null
- , userset_relation text not null
- , created_at xid8 not null
- , deleted_at xid8 not null
- -- TODO: indexes and stuff
+create table if not exists secd.impersonator (
+ impersonator_id bigint not null references secd.identity(identity_id)
+ , target_id bigint not null references secd.identity(identity_id)
+ , credential_id bigint not null references secd.credential(credential_id)
+ , created_at timestamptz not null
);
diff --git a/crates/secd/store/pg/sql/find_impersonator.sql b/crates/secd/store/pg/sql/find_impersonator.sql
new file mode 100644
index 0000000..e544598
--- /dev/null
+++ b/crates/secd/store/pg/sql/find_impersonator.sql
@@ -0,0 +1,10 @@
+select i2.identity_public_id as impersonator_public_id
+ , i3.identity_public_id as target_public_id
+ , i.created_at
+from secd.impersonator i
+join secd.identity i2 on i.impersonator_id = i2.identity_id
+join secd.identity i3 on i.target_id = i3.identity_id
+join secd.credential c using (credential_id)
+where (($1::uuid is null) or (i2.identity_public_id = $1))
+and (($2::uuid is null) or (i3.identity_public_id = $2))
+and c.revoked_at > $3;
diff --git a/crates/secd/store/pg/sql/write_impersonator.sql b/crates/secd/store/pg/sql/write_impersonator.sql
new file mode 100644
index 0000000..b67b738
--- /dev/null
+++ b/crates/secd/store/pg/sql/write_impersonator.sql
@@ -0,0 +1,11 @@
+insert into secd.impersonator (
+ impersonator_id
+ , target_id
+ , credential_id
+ , created_at
+) values (
+ (select identity_id from secd.identity where identity_public_id = $1)
+ , (select identity_id from secd.identity where identity_public_id = $2)
+ , (select credential_id from secd.credential where credential_public_id = $3)
+ , $4
+);
diff --git a/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql b/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql
index b2ce45d..0a182e1 100644
--- a/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql
+++ b/crates/secd/store/sqlite/migrations/20221125051738_bootstrap.sql
@@ -34,9 +34,7 @@ create table if not exists credential (
, deleted_at integer
);
-create unique index if not exists credential_passphrase_type_key_ix
-on credential (partial_key)
-where type = 'Passphrase';
+create unique index if not exists credential_partial_key_type_key_ix on credential (partial_key);
create table if not exists address (
address_id integer primary key
@@ -63,16 +61,6 @@ create table if not exists address_validation (
, unique(address_validation_public_id)
);
-create table if not exists session (
- session_id integer primary key
- , identity_id integer not null references identity(identity_id)
- , token_hash blob not null
- , created_at integer not null
- , expired_at integer not null
- , revoked_at integer
- , unique(token_hash)
-);
-
create table if not exists message (
message_id integer primary key
, address_id integer not null references address(address_id)
@@ -83,3 +71,10 @@ create table if not exists message (
, created_at integer not null
, sent_at integer
);
+
+create table if not exists impersonator (
+ impersonator_id integer not null references identity(identity_id)
+ , target_id integer not null references identity(identity_id)
+ , credential_id integer not null references credential(credential_id)
+ , created_at integer not null
+);
diff --git a/crates/secd/store/sqlite/sql/find_credential.sql b/crates/secd/store/sqlite/sql/find_credential.sql
index 9062914..0590dee 100644
--- a/crates/secd/store/sqlite/sql/find_credential.sql
+++ b/crates/secd/store/sqlite/sql/find_credential.sql
@@ -9,4 +9,4 @@ join identity i using (identity_id)
where (($1 is null) or (c.credential_public_id = $1))
and (($2 is null) or (i.identity_public_id = $2))
and (($3 is null) or (c.type = $3))
-and (($3 is null or $4 is null) or (c.data->$3->>'key' = $4))
+and (($3 is null or $4 is null) or (c.partial_key = $4))
diff --git a/crates/secd/store/sqlite/sql/find_identity.sql b/crates/secd/store/sqlite/sql/find_identity.sql
index 1528407..0d32a9b 100644
--- a/crates/secd/store/sqlite/sql/find_identity.sql
+++ b/crates/secd/store/sqlite/sql/find_identity.sql
@@ -7,9 +7,7 @@ select distinct
from identity i
left join address_validation av using (identity_id)
left join address a using (address_id)
-left join session s using (identity_id)
where (($1 is null) or (i.identity_public_id = $1))
and (($2 is null) or (a.value = $2))
and (($3 is null) or (($3 is true) and (av.validated_at is not null)))
-and (($4 is null) or (s.token_hash = $4))
and i.deleted_at is null;
diff --git a/crates/secd/store/sqlite/sql/find_impersonator.sql b/crates/secd/store/sqlite/sql/find_impersonator.sql
new file mode 100644
index 0000000..786e9ba
--- /dev/null
+++ b/crates/secd/store/sqlite/sql/find_impersonator.sql
@@ -0,0 +1,10 @@
+select i2.identity_public_id as impersonator_public_id
+ , i3.identity_public_id as target_public_id
+ , i.created_at
+from impersonator i
+join identity i2 on i.impersonator_id = i2.identity_id
+join identity i3 on i.target_id = i3.identity_id
+join credential c using (credential_id)
+where (($1 is null) or (i2.identity_public_id = $1))
+and (($2 is null) or (i3.identity_public_id = $2))
+and c.revoked_at > $3;
diff --git a/crates/secd/store/sqlite/sql/find_session.sql b/crates/secd/store/sqlite/sql/find_session.sql
deleted file mode 100644
index 31640dd..0000000
--- a/crates/secd/store/sqlite/sql/find_session.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-select distinct
- i.identity_public_id
- , s.created_at
- , s.expired_at
- , s.revoked_at
-from session s
-join identity i using (identity_id)
-where (($1 is null) or (s.token_hash = $1))
-and (($2 is null) or (i.identity_public_id = $2))
-and (($3 is null) or (s.expired_at > $3))
-and ((revoked_at is null) or ($4 is null) or (s.revoked_at > $4));
diff --git a/crates/secd/store/sqlite/sql/write_credential.sql b/crates/secd/store/sqlite/sql/write_credential.sql
index 3319226..06cb389 100644
--- a/crates/secd/store/sqlite/sql/write_credential.sql
+++ b/crates/secd/store/sqlite/sql/write_credential.sql
@@ -16,4 +16,6 @@ insert into credential (
, $6
, $7
, $8
-);
+) on conflict (partial_key) do update
+ set revoked_at = excluded.revoked_at
+ , deleted_at = excluded.deleted_at;
diff --git a/crates/secd/store/sqlite/sql/write_impersonator.sql b/crates/secd/store/sqlite/sql/write_impersonator.sql
new file mode 100644
index 0000000..ae81466
--- /dev/null
+++ b/crates/secd/store/sqlite/sql/write_impersonator.sql
@@ -0,0 +1,11 @@
+insert into impersonator (
+ impersonator_id
+ , target_id
+ , credential_id
+ , created_at
+) values (
+ (select identity_id from identity where identity_public_id = $1)
+ , (select identity_id from identity where identity_public_id = $2)
+ , (select credential_id from credential where credential_public_id = $3)
+ , $4
+);
diff --git a/crates/secd/store/sqlite/sql/write_session.sql b/crates/secd/store/sqlite/sql/write_session.sql
deleted file mode 100644
index 9ffb105..0000000
--- a/crates/secd/store/sqlite/sql/write_session.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-insert into session (
- identity_id
- , token_hash
- , created_at
- , expired_at
- , revoked_at
-) values (
- (select identity_id from identity where identity_public_id = $1)
- , $2, $3, $4, $5
-) on conflict (token_hash) do update
- set revoked_at = excluded.revoked_at;
diff --git a/justfile b/justfile
index d1b7058..88c8152 100644
--- a/justfile
+++ b/justfile
@@ -2,7 +2,8 @@ PG_USER := "secduser"
PG_PASS := "p4ssw0rd"
PG_DB := "secd"
PG_PORT := "5412"
-PG_URL := "postgres://" + PG_USER + ":" + PG_PASS + "@host.docker.internal:" + PG_PORT / PG_DB
+PG_URL := "postgres://" + PG_USER + ":" + PG_PASS + "@0.0.0.0:" + PG_PORT / PG_DB
+PG_DOCKER_URL := "postgres://" + PG_USER + ":" + PG_PASS + "@host.docker.internal:" + PG_PORT / PG_DB
@run-debug:
RUST_BACKTRACE=1 cargo run $@
@@ -20,16 +21,19 @@ PG_URL := "postgres://" + PG_USER + ":" + PG_PASS + "@host.docker.internal:" + P
@start-postgres: _start_postgres start-spice
@migrate-spice:
- docker run --rm --add-host host.docker.internal:host-gateway --name spice_migrator authzed/spicedb migrate head --datastore-engine postgres --datastore-conn-uri "{{PG_URL}}"
+ docker run --rm --add-host host.docker.internal:host-gateway --name spice_migrator authzed/spicedb migrate head --datastore-engine postgres --datastore-conn-uri "{{PG_DOCKER_URL}}"
@start-spice: migrate-spice
- docker start spice || docker run -d --add-host host.docker.internal:host-gateway --name spice -p 50051:50051 -p 9090:9090 -p 8080:8080 -p 9443:8443 authzed/spicedb serve --grpc-preshared-key "sup3rs3cr3tk3y" --http-enabled --datastore-engine postgres --datastore-conn-uri "{{PG_URL}}"
+ docker start spice || docker run -d --add-host host.docker.internal:host-gateway --name spice -p 50051:50051 -p 9090:9090 -p 8080:8080 -p 9443:8443 authzed/spicedb serve --grpc-preshared-key "sup3rs3cr3tk3y" --http-enabled --datastore-engine postgres --datastore-conn-uri "{{PG_DOCKER_URL}}"
-@_start_postgres:
- docker start secddb || docker run -d --name secddb -e POSTGRES_PASSWORD={{PG_PASS}} -e POSTGRES_USER={{PG_USER}} -e POSTGRES_DB={{PG_DB}} -p {{PG_PORT}}:5432 postgres:13 -c log_statement=all
+@_start_postgres: apply-secd-tables
+ docker start secddb || docker run -d --name secddb -e POSTGRES_PASSWORD={{PG_PASS}} -e POSTGRES_USER={{PG_USER}} -e POSTGRES_DB={{PG_DB}} -p {{PG_PORT}}:5432 postgres:14 -c log_statement=all
@start-mailserver:
docker start mailhog || docker run -d --name mailhog -p 7180:8025 -p 25:1025 mailhog/mailhog:latest
+@apply-secd-tables:
+ sqlx migrate run --database-url {{PG_URL}} --source crates/secd/store/pg/migrations secd;
+
@clean:
docker rm -f spice secddb