aboutsummaryrefslogtreecommitdiff
path: root/crates/iam
diff options
context:
space:
mode:
authorbenj <benj@rse8.com>2023-04-24 13:24:45 -0700
committerbenj <benj@rse8.com>2023-04-24 13:24:45 -0700
commiteb92f823c31a5e702af7005231f0d6915aad3342 (patch)
treebb624786a47accb2dfcfe95d20c00c9624c28a9c /crates/iam
parent176aae037400b43cb3971cd968afe59c73b3097a (diff)
downloadsecdiam-eb92f823c31a5e702af7005231f0d6915aad3342.tar
secdiam-eb92f823c31a5e702af7005231f0d6915aad3342.tar.gz
secdiam-eb92f823c31a5e702af7005231f0d6915aad3342.tar.bz2
secdiam-eb92f823c31a5e702af7005231f0d6915aad3342.tar.lz
secdiam-eb92f823c31a5e702af7005231f0d6915aad3342.tar.xz
secdiam-eb92f823c31a5e702af7005231f0d6915aad3342.tar.zst
secdiam-eb92f823c31a5e702af7005231f0d6915aad3342.zip
email templates, sendgrid, creds, and some experimental things
Started playing with namespace configs and integrating with zanzibar impls. Still lot's of experimenting and dead code going on.
Diffstat (limited to 'crates/iam')
-rw-r--r--crates/iam/src/api.rs212
-rw-r--r--crates/iam/src/main.rs247
2 files changed, 76 insertions, 383 deletions
diff --git a/crates/iam/src/api.rs b/crates/iam/src/api.rs
index ace3199..af175a7 100644
--- a/crates/iam/src/api.rs
+++ b/crates/iam/src/api.rs
@@ -1,7 +1,6 @@
use crate::ISSUE_TRACKER_LOC;
use clap::{Parser, Subcommand, ValueEnum};
use colored::*;
-use secd::IdentityId;
use serde::{Deserialize, Serialize};
use thiserror;
use url::Url;
@@ -81,52 +80,23 @@ pub enum Command {
object: DevObject,
},
#[command(
- about = "Get details for a specific IAM object",
+ about = "List and filter IAM objects",
long_about = "Get\n\nGet details for a specific IAM object"
)]
Get {
#[command(subcommand)]
object: GetObject,
},
+ /// Start the iam repl to more easily interact with iam and its primitives
+ Repl,
#[command(
- about = "Initialize an IAM store (alias for `iam admin init`)",
- long_about = "Init\n\nInitalize a new IAM admin store and save the store's configuration profile. This command is an alias for, and thus equiavlent to, `iam admin init`."
- )]
- Init {
- /// If true, interactively initialize an IAM store. Otherwise output a template config.
- #[arg(long, short, action)]
- interactive: bool,
- },
- #[command(
- about = "Link multiple IAM objects together",
- long_about = "Link\n\nCleave different IAM entities to create an IAM system."
- )]
- Link {
- #[command(subcommand)]
- object: LinkObject,
- /// Unlink the provided entities rather than link them.
- #[arg(long, short, action)]
- unlink: bool,
- },
- #[command(
- about = "List and filter IAM objects",
- long_about = "List\n\nPage through collections of IAM objects with optional filtering"
+ about = "Update specified IAM object",
+ long_about = "Update\n\nUpdate details for the specified IAM object"
)]
- Ls {
+ Update {
#[command(subcommand)]
- object: ListObject,
- /// Regex filter for entity names
- #[arg(long, short)]
- name: Option<String>,
- /// Only fetch entities created after this time
- #[arg(long, short)]
- after: Option<i64>,
- /// Only fetch entities created before this time
- #[arg(long, short)]
- before: Option<i64>,
+ object: UpdateObject,
},
- /// Start the iam repl to more easily interact with iam and its primitives
- Repl,
}
#[derive(Subcommand)]
@@ -232,86 +202,13 @@ pub enum VoiceProvider {
#[derive(Subcommand)]
pub enum CreateObject {
- #[command(
- about = "A set of long-lived tokens which authorize an identity",
- long_about = "Api Keys\n\nApi keys are long lived identifiers which authenticate and authorize a identity. Keys have a public and private part,\nwhich may be shared and must be kept private, respectively. Unlike sessions, api keys may be long-lived (infinite) or\nset to expire within certain timeframes."
- )]
- ApiKey {
- /// Identity against which this api key will be linked
- identity: Uuid,
- /// Time this api key expires (epoch time)
- expired_at: Option<i64>,
- },
- #[command(
- about = "A collection of identities",
- long_about = "Group\n\nA group may be created to operate simultaneously against a collection of identities. An identity may be part of mutliple groups, but it may not be part of the same group more than once."
- )]
- Group {
- /// The unique name for this group
- name: String,
- /// An optional set of identities to link against this group
- identities: Vec<Uuid>,
- },
- #[command(
- about = "A collection of services and service actions",
- long_about = "Permission\n\nA permission may be created to operate simultaneously against a collection of services and service actions. A service or service action may be part of mutliple permissions, but it may not be part of the same permission more than once. A permission may be used when many services and service actions are linked and unlinked against a role."
- )]
- Permission {
- /// An optional set of services to link against this permission
- #[arg(long, short)]
- services: Vec<Uuid>,
- /// An optional set of service actions to link against this permission
- #[arg(long, short)]
- actions: Vec<Uuid>,
- },
- #[command(
- about = "A collection of permissions",
- long_about = "Role\n\nA role may be created to operate simultaneously against a collection of permissions. A permission may be part of mutliple roles, but it may not be part of the same role more than once. A role may be used when many entities (such as groups or identities) are linked and unlinked against many permissions."
- )]
- Role {
- /// The unique name for this role
- name: String,
- /// An optional set of permissions to link against this role
- permissions: Vec<Uuid>,
- },
- #[command(
- about = "An entity for which an action may be authorized",
- long_about = "Service\n\nA service is an atomic entity which requires authorization. While a service's authorization may be subdivided by service actions, a service represents a logical element of authorization separation."
- )]
- Service {
- /// The unique name for this service
- name: String,
- /// URI for this service which may be used to resolve authorization
- #[arg(long, short)]
- uri: Option<String>,
- },
- #[command(
- about = "A specific authorization action by a service",
- long_about = "Service Action\n\nA service action is a domain specific action which defines what an identity authorization within that service. A service action may be a simple boolean value or a more complex express which is evaluated at runtime. For example, a boolean action may be something like `can_read_salary_table`, and a more complex action may be `readable_table_rows(datetime)` which executes at runtime and returns a value (or list of values) the service may use to determine authorization. Service actions are used as an inversion of control pattern to ensure that services do not need to worry about specific authorization actions for identities. A service action is unnecessary if the service has no specific authorization logic."
- )]
- ServiceAction {
- /// The unique name for this service action
- name: String,
- /// Program executed for this service action
- #[arg(long, short)]
- program: Option<String>,
- },
- #[command(
- about = "A timebound token which authorizes an identity",
- long_about = "Session\n\nA session is an opaque timebound token which allows an identity to authorize against IAM services. The session may be created by providing a validation request id and secret challenge code"
- )]
- Session {
- /// The validation id associated with a non-expired valid validation
- #[arg(long, short)]
- validation_id: Uuid,
- /// The secret code associated with this validation.
+ Credential {
+ #[command(subcommand)]
+ method: CredentialMethod,
+ /// The identity against which to associate this credential. A new identity will be created if no identity is provided.
#[arg(long, short)]
- secret_code: String,
+ identity_id: Option<Uuid>,
},
- #[command(
- about = "An action which initiates an address validation",
- long_about = "Validation\n\nA validation requires that the identity authenticate in some way, either by providing IAM managed credentials, an external gated mechanism (e.g. email, phone, or hardware key), or through a secondary authentication provider (oauth, saml, ldap, kerberos)."
- )]
Validation {
/// Method by which the validation will occur
#[command(subcommand)]
@@ -350,6 +247,17 @@ pub enum DevObject {
}
#[derive(Subcommand)]
+pub enum CredentialMethod {
+ /// A
+ Passphrase {
+ /// B
+ username: String,
+ /// C
+ passphrase: String,
+ },
+}
+
+#[derive(Subcommand)]
pub enum ValidationMethod {
/// An email address to which the validation will be sent
Email {
@@ -378,8 +286,12 @@ pub enum GetObject {
id: Option<Uuid>,
},
Identity {
+ /// The unique id corresponding to this identity.
+ #[arg(long, short)]
+ identity_id: Option<Uuid>,
/// Any session corresponding to this identity.
- session_token: String,
+ #[arg(long, short)]
+ session_token: Option<String>,
},
Permission {
/// Unique permission name
@@ -420,72 +332,16 @@ pub enum GetObject {
}
#[derive(Subcommand)]
-pub enum LinkObject {
- Group {
- group_name: String,
- #[arg(short, long, alias = "id")]
- group_id: Option<Uuid>,
-
- identity_ids: Vec<Uuid>,
- },
+pub enum UpdateObject {
Identity {
- identity_id: Uuid,
-
- group_names: Vec<String>,
- #[arg(long)]
- group_ids: Vec<Uuid>,
- },
- Permission {
- permission_name: String,
- #[arg(short, long, alias = "id")]
- permission_id: Option<Uuid>,
-
- role_names: Vec<String>,
- #[arg(long)]
- role_ids: Vec<Uuid>,
- },
- Role {
- role_name: String,
- #[arg(short, long, alias = "id")]
- role_id: Option<Uuid>,
-
- permission_names: Vec<String>,
- #[arg(long)]
- permission_ids: Vec<Uuid>,
- },
- Service {
- service_name: String,
- #[arg(short, long, alias = "id")]
- service_id: Option<Uuid>,
-
- permission_names: Vec<String>,
- #[arg(long)]
- permission_ids: Vec<Uuid>,
- },
- ServiceAction {
- service_action_name: String,
- #[arg(short, long, alias = "id")]
- service_action_id: Option<Uuid>,
-
- service_name: Vec<String>,
- #[arg(long)]
- service_ids: Vec<Uuid>,
+ /// Unique identifier for this identity.
+ id: Uuid,
+ /// Metadata for this identity. Note, structured metadata must be configured to be enforced.
+ #[arg(long, short)]
+ metadata: Option<String>,
},
}
-#[derive(Subcommand)]
-pub enum ListObject {
- ApiKey,
- Group,
- Identity,
- Permission,
- Role,
- Session,
- Service,
- ServiceAction,
- Validation,
-}
-
#[derive(Serialize, Deserialize)]
pub struct Config {
pub profile: Vec<ConfigProfile>,
diff --git a/crates/iam/src/main.rs b/crates/iam/src/main.rs
index ae44b46..c679af6 100644
--- a/crates/iam/src/main.rs
+++ b/crates/iam/src/main.rs
@@ -3,14 +3,14 @@ mod command;
mod util;
use anyhow::bail;
-use api::{
- AdminAction, Args, CliError, Command, CreateObject, DevObject, GetObject, LinkObject,
- ListObject,
-};
+use api::{AdminAction, Args, CliError, Command, CreateObject, DevObject, GetObject, UpdateObject};
use clap::Parser;
use command::dev_oauth2_listen;
use env_logger::Env;
-use secd::{auth::z, Secd, ENV_AUTH_STORE_CONN_STRING, ENV_SPICE_SECRET, ENV_SPICE_SERVER};
+use secd::{
+ auth::z, Credential, CredentialType, Secd, ENV_AUTH_STORE_CONN_STRING, ENV_SPICE_SECRET,
+ ENV_SPICE_SERVER,
+};
use util::{error_detail, Result};
use uuid::Uuid;
@@ -38,8 +38,7 @@ async fn main() {
async fn exec() -> Result<Option<String>> {
let args = Args::parse();
Ok(match args.command {
- Command::Init { interactive }
- | Command::Admin {
+ Command::Admin {
action: AdminAction::Init { interactive },
} => {
command::admin_init(interactive)
@@ -52,89 +51,25 @@ async fn exec() -> Result<Option<String>> {
// let cfg = util::read_config(args.profile).map_err(|_| CliError::InvalidProfile)?;
std::env::set_var(
ENV_AUTH_STORE_CONN_STRING,
+ // "sqlite:///home/benj/.config/secdiam/34wxC.sql?mode=rwc",
"postgresql://secduser:p4ssw0rd@localhost:5412/secd",
);
std::env::set_var(ENV_SPICE_SECRET, "sup3rs3cr3tk3y");
std::env::set_var(ENV_SPICE_SERVER, "http://[::1]:50051");
- let secd = Secd::init(Some(
- r#"
-definition user {}
-
-definition organization {
- relation r_member: user
- relation r_admin: user
-
- permission member = r_admin + r_member
- permission admin = r_admin
-}
-
-definition plugin {
- relation r_creator: user | organization#admin
- relation r_editor: user
- relation r_viewer: user
-
- permission creator = r_creator + r_creator->admin
- permission editor = r_editor + r_creator + r_creator->admin
- permission viewer = r_viewer + r_editor + r_creator + r_creator->admin
-}"#,
- ))
- .await
- .map_err(|e| CliError::SecdInitializationFailure(e.to_string()))?;
-
- secd.write(&vec![z::Relationship {
- subject: z::Subject::User((
- "user".into(),
- Uuid::parse_str("cd1e74de-6107-4191-a7b2-a142c549a9af").unwrap(),
- )),
- object: (
- "organization".into(),
- Uuid::parse_str("862f38b5-7f88-4b55-800f-af8da059e3a7").unwrap(),
- ),
- relation: "r_member".into(),
- }])
- .await
- .unwrap();
-
- let y = match secd
- .check(&z::Relationship {
- subject: z::Subject::User((
- "user".into(),
- Uuid::parse_str("cd1e74de-6107-4191-a7b2-a142c549a9af").unwrap(),
- )),
- object: (
- "organization".into(),
- Uuid::parse_str("862f38b5-7f88-4b55-800f-af8da059e3a7").unwrap(),
- ),
- relation: "member".into(),
- })
+ let secd = Secd::init(None)
.await
- {
- Ok(v) => v,
- Err(e) => panic!("fooooooooooooooooooooooooooooooooooooooooooooooo"),
- };
-
- println!("DID I HAZ IT? {:#?}", y);
+ .map_err(|e| CliError::SecdInitializationFailure(e.to_string()))?;
match rest {
Command::Admin { action } => admin(&secd, action).await?,
Command::Create { object } => create(&secd, object).await?,
Command::Dev { object } => dev(object).await?,
Command::Get { object } => get(&secd, object).await?,
- Command::Init { .. } => bail!(CliError::InternalError(error_detail(
- "4a696b66-6231-4a2f-811c-4448a41473d2",
- "Code path should be unreachable",
- ))),
- Command::Link { object, unlink } => todo!(),
- Command::Ls {
- object,
- name,
- before,
- after,
- } => todo!(),
Command::Repl => {
unimplemented!()
}
+ Command::Update { object } => update(&secd, object).await?,
}
}
})
@@ -162,37 +97,23 @@ async fn admin(secd: &Secd, cmd: AdminAction) -> Result<Option<String>> {
}
async fn create(secd: &Secd, cmd: CreateObject) -> Result<Option<String>> {
Ok(match cmd {
- CreateObject::ApiKey {
- identity,
- expired_at,
+ CreateObject::Credential {
+ method,
+ identity_id,
} => {
- println!("create object");
- None
- }
- CreateObject::Group { name, identities } => {
- println!("create group");
- None
- }
- CreateObject::Permission { services, actions } => {
- println!("create permission");
- None
- }
- CreateObject::Role { name, permissions } => {
- println!("create role");
- None
- }
- CreateObject::Service { name, uri } => {
- println!("create service");
- None
- }
- CreateObject::ServiceAction { name, program } => {
- println!("create service action");
- None
+ let t = match method {
+ api::CredentialMethod::Passphrase {
+ username,
+ passphrase,
+ } => CredentialType::Passphrase {
+ key: username,
+ value: passphrase,
+ },
+ };
+
+ let credential = secd.create_credential(t, identity_id).await?;
+ Some(serde_json::ser::to_string(&credential)?.to_string())
}
- CreateObject::Session {
- validation_id,
- secret_code,
- } => todo!(),
CreateObject::Validation {
method,
identity_id,
@@ -236,9 +157,13 @@ async fn get(secd: &Secd, cmd: GetObject) -> Result<Option<String>> {
println!("get object group");
None
}
- GetObject::Identity { session_token } => {
- Some(serde_json::ser::to_string(&secd.get_identity(&session_token).await?)?.to_string())
- }
+ GetObject::Identity {
+ identity_id,
+ session_token,
+ } => Some(
+ serde_json::ser::to_string(&secd.get_identity(identity_id, session_token).await?)?
+ .to_string(),
+ ),
GetObject::Permission { name, id } => {
println!("get object permission");
@@ -265,105 +190,17 @@ async fn get(secd: &Secd, cmd: GetObject) -> Result<Option<String>> {
}
})
}
-async fn link(secd: &Secd, cmd: LinkObject, should_unlink: bool) -> Result<Option<String>> {
- Ok(match cmd {
- LinkObject::Group {
- group_name,
- group_id,
- identity_ids,
- } => {
- println!("link object group");
- None
- }
- LinkObject::Identity {
- identity_id,
- group_names,
- group_ids,
- } => {
- println!("link object identity");
- None
- }
- LinkObject::Permission {
- permission_name,
- permission_id,
- role_names,
- role_ids,
- } => {
- println!("link object permission");
- None
- }
- LinkObject::Role {
- role_name,
- role_id,
- permission_names,
- permission_ids,
- } => {
- println!("link object role");
- None
- }
- LinkObject::Service {
- service_name,
- service_id,
- permission_names,
- permission_ids,
- } => {
- println!("link object service");
- None
- }
- LinkObject::ServiceAction {
- service_action_name,
- service_action_id,
- service_name,
- service_ids,
- } => {
- println!("link object service action");
- None
- }
- })
-}
-async fn list(
- secd: &Secd,
- cmd: ListObject,
- filter_name: Option<String>,
- filter_before: Option<i64>,
- filter_after: Option<i64>,
-) -> Result<Option<String>> {
+
+async fn update(secd: &Secd, cmd: UpdateObject) -> Result<Option<String>> {
Ok(match cmd {
- ListObject::ApiKey => {
- println!("list object api key");
- None
- }
- ListObject::Group => {
- println!("list object group");
- None
- }
- ListObject::Identity => {
- println!("list object identity");
- None
- }
- ListObject::Permission => {
- println!("list object permission");
- None
- }
- ListObject::Role => {
- println!("list object role");
- None
- }
- ListObject::Service => {
- println!("list object service");
- None
- }
- ListObject::ServiceAction => {
- println!("list object service action");
- None
- }
- ListObject::Session => {
- println!("list object session");
- None
- }
- ListObject::Validation => {
- println!("list object valiation");
- None
+ UpdateObject::Identity { id, metadata } => {
+ let identity = if metadata.is_some() {
+ secd.update_identity_metadata(id, metadata.unwrap()).await?
+ } else {
+ secd.get_identity(Some(id), None).await?
+ };
+
+ Some(serde_json::to_string(&identity)?.to_string())
}
})
}