-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WAYK-2566: Add token type + bridge token + update powershell to gener…
…ate token
- Loading branch information
Showing
16 changed files
with
127 additions
and
127 deletions.
There are no files selected for viewing
133 changes: 54 additions & 79 deletions
133
devolutions-gateway/src/http/controllers/http_bridge.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,112 +1,87 @@ | ||
use crate::config::Config; | ||
use crate::http::guards::access::{AccessGuard, JetTokenType}; | ||
use crate::http::HttpErrorStatus; | ||
use saphir::http::StatusCode; | ||
use jet_proto::token::JetAccessTokenClaims; | ||
use saphir::macros::controller; | ||
use saphir::request::Request; | ||
use saphir::response::Builder; | ||
use std::sync::Arc; | ||
|
||
pub const GATEWAY_BRIDGE_TOKEN_HDR_NAME: &str = "Gateway-Bridge-Token"; | ||
|
||
#[derive(Deserialize)] | ||
struct HttpBridgeClaims { | ||
target: url::Url, | ||
} | ||
pub const REQUEST_AUTHORIZATION_TOKEN_HDR_NAME: &str = "Request-Authorization-Token"; | ||
|
||
pub struct HttpBridgeController { | ||
config: Arc<Config>, | ||
client: reqwest::Client, | ||
} | ||
|
||
impl HttpBridgeController { | ||
pub fn new(config: Arc<Config>) -> Self { | ||
pub fn new() -> Self { | ||
let client = reqwest::Client::new(); | ||
Self { config, client } | ||
} | ||
} | ||
|
||
impl HttpBridgeController { | ||
fn h_decode_claims(&self, token_str: &str) -> Result<HttpBridgeClaims, HttpErrorStatus> { | ||
use core::convert::TryFrom; | ||
use picky::jose::jwt; | ||
use std::time::{SystemTime, UNIX_EPOCH}; | ||
|
||
let key = self | ||
.config | ||
.provisioner_public_key | ||
.as_ref() | ||
.ok_or((StatusCode::INTERNAL_SERVER_ERROR, "provisioner public key is missing"))?; | ||
|
||
let numeric_date = SystemTime::now() | ||
.duration_since(UNIX_EPOCH) | ||
.expect("UNIX EPOCH is in the past") | ||
.as_secs(); | ||
let date = jwt::JwtDate::new_with_leeway(i64::try_from(numeric_date).unwrap(), 60); | ||
let validator = jwt::JwtValidator::strict(&date); | ||
|
||
let jws = jwt::JwtSig::decode(token_str, key, &validator).map_err(HttpErrorStatus::forbidden)?; | ||
|
||
Ok(jws.claims) | ||
Self { client } | ||
} | ||
} | ||
|
||
#[controller(name = "bridge")] | ||
impl HttpBridgeController { | ||
#[post("/message")] | ||
#[guard(AccessGuard, init_expr = r#"JetTokenType::Bridge"#)] | ||
async fn message(&self, req: Request) -> Result<Builder, HttpErrorStatus> { | ||
use core::convert::TryFrom; | ||
|
||
// FIXME: when updating reqwest 0.10 → 0.11 and hyper 0.13 → 0.14: | ||
// Use https://docs.rs/reqwest/0.11.4/reqwest/struct.Body.html#impl-From%3CBody%3E | ||
// to get a streaming reqwest Request instead of loading the whole body in memory. | ||
let req = req.load_body().await.map_err(HttpErrorStatus::internal)?; | ||
let req: saphir::request::Request<reqwest::Body> = req.map(reqwest::Body::from); | ||
let mut req: http::Request<reqwest::Body> = http::Request::from(req); | ||
if let Some(JetAccessTokenClaims::Bridge(claims)) = req | ||
.extensions() | ||
.get::<JetAccessTokenClaims>() | ||
.map(|claim| claim.clone()) | ||
{ | ||
// FIXME: when updating reqwest 0.10 → 0.11 and hyper 0.13 → 0.14: | ||
// Use https://docs.rs/reqwest/0.11.4/reqwest/struct.Body.html#impl-From%3CBody%3E | ||
// to get a streaming reqwest Request instead of loading the whole body in memory. | ||
let req = req.load_body().await.map_err(HttpErrorStatus::internal)?; | ||
let req: saphir::request::Request<reqwest::Body> = req.map(reqwest::Body::from); | ||
let mut req: http::Request<reqwest::Body> = http::Request::from(req); | ||
|
||
// === Replace Authorization header (used to be authorized on the gateway) with the request authorization token === // | ||
|
||
let mut rsp = { | ||
let headers = req.headers_mut(); | ||
headers.remove(http::header::AUTHORIZATION); | ||
if let Some(auth_token) = headers.remove(REQUEST_AUTHORIZATION_TOKEN_HDR_NAME) { | ||
headers.insert(http::header::AUTHORIZATION, auth_token); | ||
} | ||
|
||
// === Filter and validate request to forward === // | ||
// Update request destination | ||
let uri = http::Uri::try_from(claims.target.as_str()).map_err(HttpErrorStatus::bad_request)?; | ||
*req.uri_mut() = uri; | ||
|
||
let mut rsp = { | ||
// Gateway Bridge Claims | ||
let headers = req.headers_mut(); | ||
let token_hdr = headers | ||
.remove(GATEWAY_BRIDGE_TOKEN_HDR_NAME) | ||
.ok_or((StatusCode::BAD_REQUEST, "Gateway-Bridge-Token header is missing"))?; | ||
let token_str = token_hdr.to_str().map_err(HttpErrorStatus::bad_request)?; | ||
let claims = self.h_decode_claims(token_str)?; | ||
// Forward | ||
slog_scope::debug!("Forward HTTP request to {}", req.uri()); | ||
let req = reqwest::Request::try_from(req).map_err(HttpErrorStatus::internal)?; | ||
self.client.execute(req).await.map_err(HttpErrorStatus::bad_gateway)? | ||
}; | ||
|
||
// Update request destination | ||
let uri = http::Uri::try_from(claims.target.as_str()).map_err(HttpErrorStatus::bad_request)?; | ||
*req.uri_mut() = uri; | ||
// === Create HTTP response using target response === // | ||
|
||
// Forward | ||
slog_scope::debug!("Forward HTTP request to {}", req.uri()); | ||
let req = reqwest::Request::try_from(req).map_err(HttpErrorStatus::internal)?; | ||
self.client.execute(req).await.map_err(HttpErrorStatus::bad_gateway)? | ||
}; | ||
let mut rsp_builder = Builder::new(); | ||
|
||
// === Create HTTP response using target response === // | ||
{ | ||
// Status code | ||
rsp_builder = rsp_builder.status(rsp.status()); | ||
|
||
let mut rsp_builder = Builder::new(); | ||
// Headers | ||
let headers = rsp_builder.headers_mut().unwrap(); | ||
rsp.headers_mut().drain().for_each(|(name, value)| { | ||
if let Some(name) = name { | ||
headers.insert(name, value); | ||
} | ||
}); | ||
|
||
{ | ||
// Status code | ||
rsp_builder = rsp_builder.status(rsp.status()); | ||
|
||
// Headers | ||
let headers = rsp_builder.headers_mut().unwrap(); | ||
rsp.headers_mut().drain().for_each(|(name, value)| { | ||
if let Some(name) = name { | ||
headers.insert(name, value); | ||
// Body | ||
match rsp.bytes().await { | ||
Ok(body) => rsp_builder = rsp_builder.body(body), | ||
Err(e) => slog_scope::warn!("Couldn’t get bytes from response body: {}", e), | ||
} | ||
}); | ||
|
||
// Body | ||
match rsp.bytes().await { | ||
Ok(body) => rsp_builder = rsp_builder.body(body), | ||
Err(e) => slog_scope::warn!("Couldn’t get bytes from response body: {}", e), | ||
} | ||
} | ||
|
||
Ok(rsp_builder) | ||
Ok(rsp_builder) | ||
} else { | ||
Err(HttpErrorStatus::unauthorized("Bridge token is mandatory")) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,44 @@ | ||
use crate::http::HttpErrorStatus; | ||
use jet_proto::token::{JetAccessScope, JetAccessTokenClaims}; | ||
use saphir::prelude::*; | ||
|
||
#[derive(Deserialize)] | ||
pub enum JetAccessType { | ||
pub enum JetTokenType { | ||
Scope(JetAccessScope), | ||
Session, | ||
Bridge, | ||
Association, | ||
} | ||
|
||
pub struct AccessGuard { | ||
access_type: JetAccessType, | ||
token_type: JetTokenType, | ||
} | ||
|
||
#[guard] | ||
impl AccessGuard { | ||
pub fn new(access_type: JetAccessType) -> Self { | ||
AccessGuard { access_type } | ||
pub fn new(token_type: JetTokenType) -> Self { | ||
AccessGuard { token_type } | ||
} | ||
|
||
async fn validate(&self, req: Request) -> Result<Request, StatusCode> { | ||
async fn validate(&self, req: Request) -> Result<Request, HttpErrorStatus> { | ||
if let Some(claims) = req.extensions().get::<JetAccessTokenClaims>() { | ||
match (claims, &self.access_type) { | ||
(JetAccessTokenClaims::Session(_), JetAccessType::Session) => { | ||
match (claims, &self.token_type) { | ||
(JetAccessTokenClaims::Association(_), JetTokenType::Association) => { | ||
return Ok(req); | ||
} | ||
(JetAccessTokenClaims::Scope(scope_from_request), JetAccessType::Scope(scope_needed)) | ||
(JetAccessTokenClaims::Scope(scope_from_request), JetTokenType::Scope(scope_needed)) | ||
if scope_from_request.scope == *scope_needed => | ||
{ | ||
return Ok(req); | ||
} | ||
(JetAccessTokenClaims::Bridge(_), JetTokenType::Bridge) => { | ||
return Ok(req); | ||
} | ||
_ => {} | ||
} | ||
} | ||
|
||
Err(StatusCode::FORBIDDEN) | ||
Err(HttpErrorStatus::forbidden( | ||
"Token provided can't be used to access the route", | ||
)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.