Skip to content

Commit

Permalink
feat(gateway): Add API to trigger Gateway update
Browse files Browse the repository at this point in the history
  • Loading branch information
pacmancoder committed Jun 21, 2024
1 parent 5256b9f commit 4a8ecee
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 1 deletion.
1 change: 1 addition & 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 devolutions-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ openapi = ["dep:utoipa"]
# In-house
transport = { path = "../crates/transport" }
jmux-proxy = { path = "../crates/jmux-proxy" }
devolutions-agent-shared = { path = "../crates/devolutions-agent-shared" }
devolutions-gateway-task = { path = "../crates/devolutions-gateway-task" }
devolutions-log = { path = "../crates/devolutions-log" }
ironrdp-pdu = { version = "0.1", git = "https://github.com/Devolutions/IronRDP", rev = "4844e77b7f65024d85ba74b1824013eda6eb32b2" }
Expand Down
Binary file modified devolutions-gateway/openapi/gateway-api.yaml
Binary file not shown.
Binary file modified devolutions-gateway/openapi/subscriber-api.yaml
Binary file not shown.
4 changes: 3 additions & 1 deletion devolutions-gateway/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod net;
pub mod rdp;
pub mod session;
pub mod sessions;
pub mod update;
pub mod webapp;

pub fn make_router<S>(state: crate::DgwState) -> axum::Router<S> {
Expand All @@ -27,7 +28,8 @@ pub fn make_router<S>(state: crate::DgwState) -> axum::Router<S> {
.route("/jet/rdp", axum::routing::get(rdp::handler))
.nest("/jet/fwd", fwd::make_router(state.clone()))
.nest("/jet/webapp", webapp::make_router(state.clone()))
.nest("/jet/net", net::make_router(state.clone()));
.nest("/jet/net", net::make_router(state.clone()))
.route("/jet/update", axum::routing::get(update::start_update));

if state.conf_handle.get_conf().web_app.enabled {
router = router.route(
Expand Down
63 changes: 63 additions & 0 deletions devolutions-gateway/src/api/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use axum::extract::Query;
use axum::Json;
use hyper::StatusCode;

use devolutions_agent_shared::{get_updater_file_path, ProductUpdateInfo, UpdateJson, VersionSpecification};

use crate::extract::UpdateScope;
use crate::http::{HttpError, HttpErrorBuilder};

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct UpdateQueryParam {
version: VersionSpecification,
}

#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Serialize)]
pub(crate) struct UpdateResponse {}

/// Starts Devolutions Gateway update process
#[cfg_attr(feature = "openapi", utoipa::path(
get,
operation_id = "Update",
tag = "Update",
path = "/jet/update",
responses(
(status = 200, description = "Update request has been processed successfully", body = UpdateResponse),
(status = 400, description = "Bad request"),
(status = 401, description = "Invalid or missing authorization token"),
(status = 403, description = "Insufficient permissions"),
(status = 500, description = "Agent updater service is malfunctioning"),
(status = 503, description = "Agent updater service is unavailable"),
),
security(("scope_token" = ["gateway.update"])),
))]
pub(super) async fn start_update(
Query(query): Query<UpdateQueryParam>,
_scope: UpdateScope,
) -> Result<Json<UpdateResponse>, HttpError> {
let target_version = query.version;

let updater_file_path = get_updater_file_path();

if !updater_file_path.exists() {
error!("Failed to start Gateway update, `update.json` does not exist (should be created by Devolutions Agent)");

return Err(HttpErrorBuilder::new(StatusCode::SERVICE_UNAVAILABLE).msg("Agent updater service is unavailable"));
}

let update_json = UpdateJson {
gateway: Some(ProductUpdateInfo { target_version }),
};

serde_json::to_string(&update_json)
.ok()
.and_then(|serialized| std::fs::write(updater_file_path, serialized).ok())
.ok_or_else(|| {
error!("Failed to write new Gateway version to `update.json`");
HttpErrorBuilder::new(StatusCode::INTERNAL_SERVER_ERROR).msg("Agent updater service is unavailable")
})?;

Ok(Json(UpdateResponse {}))
}
19 changes: 19 additions & 0 deletions devolutions-gateway/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,25 @@ where
}
}

#[derive(Clone, Copy)]
pub struct UpdateScope;

#[async_trait]
impl<S> FromRequestParts<S> for UpdateScope
where
S: Send + Sync,
{
type Rejection = HttpError;

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
match ScopeToken::from_request_parts(parts, state).await?.0.scope {
AccessScope::Wildcard => Ok(Self),
AccessScope::Update => Ok(Self),
_ => Err(HttpError::forbidden().msg("invalid scope for route")),
}
}
}

#[derive(Clone)]
pub struct WebAppToken(pub WebAppTokenClaims);

Expand Down
2 changes: 2 additions & 0 deletions devolutions-gateway/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use uuid::Uuid;
crate::api::jrec::pull_recording_file,
crate::api::webapp::sign_app_token,
crate::api::webapp::sign_session_token,
crate::api::update::start_update,
// crate::api::net::get_net_config,
),
components(schemas(
Expand All @@ -39,6 +40,7 @@ use uuid::Uuid;
crate::token::AccessScope,
crate::api::webapp::AppTokenSignRequest,
crate::api::webapp::AppTokenContentType,
crate::api::update::UpdateResponse,
// crate::api::net::NetworkInterface,
SessionTokenContentType,
SessionTokenSignRequest,
Expand Down
2 changes: 2 additions & 0 deletions devolutions-gateway/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ pub enum AccessScope {
RecordingDelete,
#[serde(rename = "gateway.recordings.read")]
RecordingsRead,
#[serde(rename = "gateway.update")]
Update,
}

#[derive(Clone, Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions utils/dotnet/Devolutions.Gateway.Utils/src/AccessScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal AccessScope(string value)
public static AccessScope GatewayHeartbeatRead = new AccessScope("gateway.heartbeat.read");
public static AccessScope GatewayRecordingDelete = new AccessScope("gateway.recording.delete");
public static AccessScope GatewayRecordingsRead = new AccessScope("gateway.recordings.read");
public static AccessScope GatewayUpdate = new AccessScope("gateway.update");

public override string? ToString()
{
Expand Down

0 comments on commit 4a8ecee

Please sign in to comment.