Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self-service credentials management #1145

Merged
merged 11 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ serde = "1.0"
serde_json = "1.0"
russh = { version = "0.46.0", features = ["legacy-ed25519-pkcs8-parser"] }
russh-keys = { version = "0.46.0", features = ["legacy-ed25519-pkcs8-parser"] }
tracing = "0.1"

[profile.release]
lto = true
Expand Down
2 changes: 1 addition & 1 deletion warpgate-admin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ serde.workspace = true
serde_json.workspace = true
thiserror = "1.0"
tokio = { version = "1.20", features = ["tracing"] }
tracing = "0.1"
tracing.workspace = true
uuid = { version = "1.3", features = ["v4", "serde"] }
warpgate-common = { version = "*", path = "../warpgate-common" }
warpgate-core = { version = "*", path = "../warpgate-core" }
Expand Down
17 changes: 6 additions & 11 deletions warpgate-admin/src/api/known_hosts_detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use poem_openapi::{ApiResponse, OpenApi};
use sea_orm::{DatabaseConnection, EntityTrait, ModelTrait};
use tokio::sync::Mutex;
use uuid::Uuid;
use warpgate_common::WarpgateError;

use super::TokenSecurityScheme;
use super::AnySecurityScheme;
pub struct Api;

#[derive(ApiResponse)]
Expand All @@ -30,22 +31,16 @@ impl Api {
&self,
db: Data<&Arc<Mutex<DatabaseConnection>>>,
id: Path<Uuid>,
_auth: TokenSecurityScheme,
) -> poem::Result<DeleteSSHKnownHostResponse> {
_auth: AnySecurityScheme,
) -> Result<DeleteSSHKnownHostResponse, WarpgateError> {
use warpgate_db_entities::KnownHost;
let db = db.lock().await;

let known_host = KnownHost::Entity::find_by_id(id.0)
.one(&*db)
.await
.map_err(poem::error::InternalServerError)?;
let known_host = KnownHost::Entity::find_by_id(id.0).one(&*db).await?;

match known_host {
Some(known_host) => {
known_host
.delete(&*db)
.await
.map_err(poem::error::InternalServerError)?;
known_host.delete(&*db).await?;
Ok(DeleteSSHKnownHostResponse::Deleted)
}
None => Ok(DeleteSSHKnownHostResponse::NotFound),
Expand Down
12 changes: 5 additions & 7 deletions warpgate-admin/src/api/known_hosts_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ use poem_openapi::payload::Json;
use poem_openapi::{ApiResponse, OpenApi};
use sea_orm::{DatabaseConnection, EntityTrait};
use tokio::sync::Mutex;
use warpgate_common::WarpgateError;
use warpgate_db_entities::KnownHost;

use super::TokenSecurityScheme;
use super::AnySecurityScheme;

pub struct Api;

Expand All @@ -27,15 +28,12 @@ impl Api {
async fn api_ssh_get_all_known_hosts(
&self,
db: Data<&Arc<Mutex<DatabaseConnection>>>,
_auth: TokenSecurityScheme,
) -> poem::Result<GetSSHKnownHostsResponse> {
_auth: AnySecurityScheme,
) -> Result<GetSSHKnownHostsResponse, WarpgateError> {
use warpgate_db_entities::KnownHost;

let db = db.lock().await;
let hosts = KnownHost::Entity::find()
.all(&*db)
.await
.map_err(poem::error::InternalServerError)?;
let hosts = KnownHost::Entity::find().all(&*db).await?;
Ok(GetSSHKnownHostsResponse::Ok(Json(hosts)))
}
}
12 changes: 5 additions & 7 deletions warpgate-admin/src/api/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ use poem_openapi::{ApiResponse, Object, OpenApi};
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder, QuerySelect};
use tokio::sync::Mutex;
use uuid::Uuid;
use warpgate_common::WarpgateError;
use warpgate_db_entities::LogEntry;

use super::TokenSecurityScheme;
use super::AnySecurityScheme;

pub struct Api;

Expand All @@ -36,8 +37,8 @@ impl Api {
&self,
db: Data<&Arc<Mutex<DatabaseConnection>>>,
body: Json<GetLogsRequest>,
_auth: TokenSecurityScheme,
) -> poem::Result<GetLogsResponse> {
_auth: AnySecurityScheme,
) -> Result<GetLogsResponse, WarpgateError> {
use warpgate_db_entities::LogEntry;

let db = db.lock().await;
Expand Down Expand Up @@ -67,10 +68,7 @@ impl Api {
);
}

let logs = q
.all(&*db)
.await
.map_err(poem::error::InternalServerError)?;
let logs = q.all(&*db).await?;
let logs = logs
.into_iter()
.map(Into::into)
Expand Down
16 changes: 15 additions & 1 deletion warpgate-admin/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,26 @@ mod sso_credentials;
mod targets;
mod tickets_detail;
mod tickets_list;
mod users;
pub mod users;
mod parameters;

#[derive(SecurityScheme)]
#[oai(ty = "api_key", key_name = "X-Warpgate-Token", key_in = "header")]
#[allow(dead_code)]
pub struct TokenSecurityScheme(ApiKey);

#[derive(SecurityScheme)]
#[oai(ty = "api_key", key_name = "warpgate-http-session", key_in = "cookie")]
#[allow(dead_code)]
pub struct CookieSecurityScheme(ApiKey);

#[derive(SecurityScheme)]
#[allow(dead_code)]
pub enum AnySecurityScheme {
Token(TokenSecurityScheme),
Cookie(CookieSecurityScheme),
}

pub fn get() -> impl OpenApi {
(
(sessions_list::Api, sessions_detail::Api),
Expand All @@ -45,5 +58,6 @@ pub fn get() -> impl OpenApi {
public_key_credentials::DetailApi,
),
(otp_credentials::ListApi, otp_credentials::DetailApi),
parameters::Api,
)
}
24 changes: 10 additions & 14 deletions warpgate-admin/src/api/otp_credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use uuid::Uuid;
use warpgate_common::{UserTotpCredential, WarpgateError};
use warpgate_db_entities::OtpCredential;

use super::TokenSecurityScheme;
use super::AnySecurityScheme;

#[derive(Object)]
struct ExistingOtpCredential {
Expand Down Expand Up @@ -63,15 +63,14 @@ impl ListApi {
&self,
db: Data<&Arc<Mutex<DatabaseConnection>>>,
user_id: Path<Uuid>,
_auth: TokenSecurityScheme,
) -> poem::Result<GetOtpCredentialsResponse> {
_auth: AnySecurityScheme,
) -> Result<GetOtpCredentialsResponse, WarpgateError> {
let db = db.lock().await;

let objects = OtpCredential::Entity::find()
.filter(OtpCredential::Column::UserId.eq(*user_id))
.all(&*db)
.await
.map_err(poem::error::InternalServerError)?;
.await?;

Ok(GetOtpCredentialsResponse::Ok(Json(
objects.into_iter().map(Into::into).collect(),
Expand All @@ -88,8 +87,8 @@ impl ListApi {
db: Data<&Arc<Mutex<DatabaseConnection>>>,
body: Json<NewOtpCredential>,
user_id: Path<Uuid>,
_auth: TokenSecurityScheme,
) -> poem::Result<CreateOtpCredentialResponse> {
_auth: AnySecurityScheme,
) -> Result<CreateOtpCredentialResponse, WarpgateError> {
let db = db.lock().await;

let object = OtpCredential::ActiveModel {
Expand Down Expand Up @@ -127,22 +126,19 @@ impl DetailApi {
db: Data<&Arc<Mutex<DatabaseConnection>>>,
user_id: Path<Uuid>,
id: Path<Uuid>,
_auth: TokenSecurityScheme,
) -> poem::Result<DeleteCredentialResponse> {
_auth: AnySecurityScheme,
) -> Result<DeleteCredentialResponse, WarpgateError> {
let db = db.lock().await;

let Some(role) = OtpCredential::Entity::find_by_id(id.0)
.filter(OtpCredential::Column::UserId.eq(*user_id))
.one(&*db)
.await
.map_err(poem::error::InternalServerError)?
.await?
else {
return Ok(DeleteCredentialResponse::NotFound);
};

role.delete(&*db)
.await
.map_err(poem::error::InternalServerError)?;
role.delete(&*db).await?;
Ok(DeleteCredentialResponse::Deleted)
}
}
13 changes: 4 additions & 9 deletions warpgate-admin/src/api/pagination.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use poem_openapi::types::{ParseFromJSON, ToJSON};
use poem_openapi::Object;
use sea_orm::{ConnectionTrait, EntityTrait, FromQueryResult, PaginatorTrait, QuerySelect, Select};
use warpgate_common::WarpgateError;

#[derive(Object)]
pub struct PaginatedResponse<T: ParseFromJSON + ToJSON + Send + Sync> {
Expand All @@ -20,7 +21,7 @@ impl<T: ParseFromJSON + ToJSON + Send + Sync> PaginatedResponse<T> {
params: PaginationParams,
db: &'_ C,
postprocess: P,
) -> poem::Result<PaginatedResponse<T>>
) -> Result<PaginatedResponse<T>, WarpgateError>
where
E: EntityTrait<Model = M>,
C: ConnectionTrait,
Expand All @@ -32,17 +33,11 @@ impl<T: ParseFromJSON + ToJSON + Send + Sync> PaginatedResponse<T> {

let paginator = query.clone().paginate(db, limit);

let total = paginator
.num_items()
.await
.map_err(poem::error::InternalServerError)?;
let total = paginator.num_items().await?;

let query = query.offset(offset).limit(limit);

let items = query
.all(db)
.await
.map_err(poem::error::InternalServerError)?;
let items = query.all(db).await?;

let items = items.into_iter().map(postprocess).collect::<Vec<_>>();
Ok(PaginatedResponse {
Expand Down
78 changes: 78 additions & 0 deletions warpgate-admin/src/api/parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use poem::web::Data;
use poem_openapi::payload::Json;
use poem_openapi::{ApiResponse, Object, OpenApi};
use sea_orm::{EntityTrait, Set};
use serde::Serialize;
use warpgate_common::WarpgateError;
use warpgate_core::Services;
use warpgate_db_entities::Parameters;

use super::AnySecurityScheme;

pub struct Api;

#[derive(Serialize, Object)]
struct ParameterValues {
pub allow_own_credential_management: bool,
}

#[derive(Serialize, Object)]
struct ParameterUpdate {
pub allow_own_credential_management: Option<bool>,
}

#[derive(ApiResponse)]
enum GetParametersResponse {
#[oai(status = 200)]
Ok(Json<ParameterValues>),
}

#[derive(ApiResponse)]
enum UpdateParametersResponse {
#[oai(status = 201)]
Done,
}

#[OpenApi]
impl Api {
#[oai(path = "/parameters", method = "get", operation_id = "get_parameters")]
async fn api_get(
&self,
services: Data<&Services>,
_auth: AnySecurityScheme,
) -> Result<GetParametersResponse, WarpgateError> {
let db = services.db.lock().await;
let parameters = Parameters::Entity::get(&db).await?;

Ok(GetParametersResponse::Ok(Json(ParameterValues {
allow_own_credential_management: parameters.allow_own_credential_management,
})))
}

#[oai(
path = "/parameters",
method = "patch",
operation_id = "update_parameters"
)]
async fn api_update_parameters(
&self,
services: Data<&Services>,
body: Json<ParameterUpdate>,
_auth: AnySecurityScheme,
) -> Result<UpdateParametersResponse, WarpgateError> {
let db = services.db.lock().await;

let mut am = Parameters::ActiveModel {
id: Set(Parameters::Entity::get(&db).await?.id),
..Default::default()
};

if let Some(value) = body.allow_own_credential_management {
am.allow_own_credential_management = Set(value);
};

Parameters::Entity::update(am).exec(&*db).await?;

Ok(UpdateParametersResponse::Done)
}
}
Loading
Loading