use crate::ISSUE_TRACKER_LOC; use clap::{Parser, Subcommand, ValueEnum}; use colored::*; use serde::{Deserialize, Serialize}; use thiserror; use url::Url; use uuid::Uuid; #[derive(Debug, thiserror::Error)] pub enum CliError { #[error("{} {}", "Failed to initialize an iam store.".red(), format!("An invariant was likely broken and should be reported as a bug here: {}", ISSUE_TRACKER_LOC))] AdminInitializationError, #[error("{} {}", "Failed to recieve incoming request.".red(), .0.white())] DevOauthServer(String), #[error("{} {} {}", "An unknown error occurred.".red(), format!("An invariant was likely broken and should be reported as a bug here: {}", ISSUE_TRACKER_LOC), .0.yellow())] InternalError(String), #[error("{} {}", "The provided validation id and code is invalid or has expired.".red(), "You may recieve at most one session with a valid code, after which a new validation is required.")] InvalidCode, #[error("{}", "iam failed to read a valid configuration profile. Initialize an iam store with `iam admin init`".red())] InvalidProfile, #[error("{} {}", "Failed to initialize secd: ".red(), .0.yellow())] SecdInitializationFailure(String), } #[derive(Parser)] #[command( name = "iam", author = "benabel", version = "0.0.1", long_about = "SecD IAM\nIdentity and access management secured by secd and user controlled. Get started with `iam init`" )] pub struct Args { #[command(subcommand)] pub command: Command, /// IAM backend profile as defined in the iam config or as a connection string to an auth store. #[arg(long, short)] pub profile: Option, /// The store type for the associated connection string #[arg(long, short)] pub store_type: Option, /// Connection string for the IAM store #[arg(long, short)] pub connection_string: Option, } #[derive(Clone, ValueEnum)] pub enum StoreType { Dynamo, Memory, Mysql, Postgres, Redis, Sqlite, } #[derive(Subcommand)] pub enum Command { #[command( about = "Administrative actions for this IAM instance", long_about = "Admin\n\nAdministrative actions for this IAM instance. Each IAM instance is defined by the specified backend in the iam config or manually as an optional argument" )] Admin { #[command(subcommand)] action: AdminAction, }, #[command( about = "Create a new IAM object which may be used to enforce authorization schemes.", long_about = "Create\n\nEntities which define the structure of an IAM instance. These entities may be rendered as an IAM graph, or within a web view, to more easily visualize and manipulate the IAM instance." )] Create { #[command(subcommand)] object: CreateObject, }, #[command( about = "Utility and convenience commands while developing against secd", long_about = "Dev\n\nUtility and convenience commands while developing against secd. Easily retrieve local mail, monitor secd logs, and otherwise inspect or interact with the system." )] Dev { #[command(subcommand)] object: DevObject, }, #[command( 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 = "Update specified IAM object", long_about = "Update\n\nUpdate details for the specified IAM object" )] Update { #[command(subcommand)] object: UpdateObject, }, } #[derive(Subcommand)] pub enum AdminAction { /// Aliased as `iam init` Init { /// If true, interactively initialize an IAM store. Otherwise output a template config. #[arg(long, short, action)] interactive: bool, }, /// Configure, describe, or rotate the default IAM store. Backend { #[command(subcommand)] action: AdminBackendAction, }, /// Create a new administrative entity for an IAM store. Create { #[command(subcommand)] object: AdminObject, }, /// Seal the configured IAM store to prevent administrative changes Seal, /// Unseal the configured IAM store to make administrative changes Unseal { /// The secret key used to seal this store secret_key: String, }, } #[derive(Subcommand)] pub enum AdminBackendAction { Configure { name: String, store: StoreType, connection: String, }, Switch { name: String, }, } #[derive(Subcommand)] pub enum AdminObject { /// An email template used for IAM procedures, including identity validation EmailTemplate { template_type: EmailTemplateType, template: String, }, /// A selected provider capable of sending email messages EmailProvider { provider: EmailProvider, secret_key: String, public_key: Option, }, /// A selected Oauth2.0 provider capable of authenticating identities Oauth2Provider { client_id: String, secret: String, redirect_url: Url, }, /// A selected provider capable of sending SMS SmsProvider { provider: SmsProvider, secret_key: String, public_key: Option, }, /// A new secret which may be used to unseal the IAM store StoreSecret, /// A selected provider capable of sending automated voice messages VoiceProvider { provider: VoiceProvider, secret_key: String, public_key: Option, }, } #[derive(Clone, ValueEnum)] pub enum EmailTemplateType { Login, SignUp, } #[derive(Clone, ValueEnum)] pub enum EmailProvider { Custom, Mailgun, Sendgrid, Ses, } #[derive(Clone, ValueEnum)] pub enum SmsProvider { AwsSns, Custom, Twilio, } #[derive(Clone, ValueEnum)] pub enum VoiceProvider { Custom, Twilio, } #[derive(Subcommand)] pub enum CreateObject { 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)] identity_id: Option, }, Validation { /// Method by which the validation will occur #[command(subcommand)] method: ValidationMethod, /// The identity against which to associate this validation. A new identity will be created if no identity is provided. #[arg(long, short)] identity_id: Option, }, #[command( about = "An action which completes an address validation", long_about = "Validation Completion\n\nA validation completion depends on an existing address validation, which is validated based on the provided validation id and secret token or secret code" )] ValidationCompletion { /// The validation id against which to complete the validation. validation_id: Uuid, /// The secret token for the validation. A token or code must be provided. #[arg(long, short)] token: Option, /// The secret code for the validation. A code or token must be provided. #[arg(long, short)] code: Option, }, } #[derive(Subcommand)] pub enum DevObject { #[command( about = "Create a temporary server to easily receive oauth validation during development.", long_about = "Oauth2\n\nCreate a temporary server to easily receive oauth validation during development." )] Oauth2Server { /// The port on which the server should listen. You must specify this exact port with your oauth provider. Defaults to 1337 #[arg(long, short)] port: Option, }, } #[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 { /// Email address which will receive the validation address: String, }, /// A phone which an identity may authenticate via SMS or Voice Phone { /// Whether to use a voice code. Otherwise, uses SMS #[arg(long, short, action)] use_voice: bool, }, } #[derive(Subcommand)] pub enum GetObject { ApiKey { /// Public key associated with this api key set public_key: String, }, Group { /// Unique group name name: String, /// Unique group id #[arg(long, short)] id: Option, }, Identity { /// The unique id corresponding to this identity. #[arg(long, short)] identity_id: Option, /// Any session corresponding to this identity. #[arg(long, short)] session_token: Option, }, Permission { /// Unique permission name name: String, /// Unique permission id #[arg(long, short)] id: Option, }, Role { /// Unique role name name: String, /// Unique role id #[arg(long, short)] id: Option, }, Session { /// The plaintext token which uniquely identifies the session secret: String, }, Service { /// Unique service name name: String, /// Unique service id #[arg(long, short)] id: Option, }, ServiceAction { /// Unique service action name name: String, /// Unique service action id #[arg(long, short)] id: Option, }, Validation { /// Unique validation request id id: Uuid, }, } #[derive(Subcommand)] pub enum UpdateObject { Identity { /// 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, }, } #[derive(Serialize, Deserialize)] pub struct Config { pub profile: Vec, } #[derive(Serialize, Deserialize)] pub struct ConfigProfile { pub name: String, pub store: secd::AuthStore, pub store_conn: String, pub emailer: secd::AuthEmailMessenger, pub email_template_login: Option, pub email_template_signup: Option, } #[derive(Serialize, Deserialize)] pub struct Validation { pub validation_id: Uuid, #[serde(skip_serializing_if = "Option::is_none")] pub oauth_auth_url: Option, #[serde(skip_serializing_if = "Option::is_none")] pub note: Option, } pub type ValidationSecretCode = String;