Perform gateway prelogin when failed to login to gateway

This commit is contained in:
Kevin Yue 2024-01-23 09:17:30 -05:00
parent 9655b735a1
commit aac401e7ee
6 changed files with 69 additions and 34 deletions

10
Cargo.lock generated
View File

@ -1423,7 +1423,7 @@ dependencies = [
[[package]] [[package]]
name = "gpapi" name = "gpapi"
version = "2.0.0-beta6" version = "2.0.0-beta7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.21.5", "base64 0.21.5",
@ -1452,7 +1452,7 @@ dependencies = [
[[package]] [[package]]
name = "gpauth" name = "gpauth"
version = "2.0.0-beta6" version = "2.0.0-beta7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1472,7 +1472,7 @@ dependencies = [
[[package]] [[package]]
name = "gpclient" name = "gpclient"
version = "2.0.0-beta6" version = "2.0.0-beta7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1493,7 +1493,7 @@ dependencies = [
[[package]] [[package]]
name = "gpservice" name = "gpservice"
version = "2.0.0-beta6" version = "2.0.0-beta7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@ -2478,7 +2478,7 @@ dependencies = [
[[package]] [[package]]
name = "openconnect" name = "openconnect"
version = "2.0.0-beta6" version = "2.0.0-beta7"
dependencies = [ dependencies = [
"cc", "cc",
"is_executable", "is_executable",

View File

@ -4,7 +4,7 @@ resolver = "2"
members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth"] members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth"]
[workspace.package] [workspace.package]
version = "2.0.0-beta6" version = "2.0.0-beta7"
authors = ["Kevin Yue <k3vinyue@gmail.com>"] authors = ["Kevin Yue <k3vinyue@gmail.com>"]
homepage = "https://github.com/yuezk/GlobalProtect-openconnect" homepage = "https://github.com/yuezk/GlobalProtect-openconnect"
edition = "2021" edition = "2021"

View File

@ -71,18 +71,21 @@ impl<'a> ConnectHandler<'a> {
Self { args, shared_args } Self { args, shared_args }
} }
pub(crate) async fn handle(&self) -> anyhow::Result<()> { fn build_gp_params(&self) -> GpParams {
let portal = utils::normalize_server(self.args.server.as_str())?; GpParams::builder()
let gp_params = GpParams::builder()
.user_agent(&self.args.user_agent) .user_agent(&self.args.user_agent)
.client_os(ClientOs::from(&self.args.os)) .client_os(ClientOs::from(&self.args.os))
.os_version(self.args.os_version()) .os_version(self.args.os_version())
.ignore_tls_errors(self.shared_args.ignore_tls_errors) .ignore_tls_errors(self.shared_args.ignore_tls_errors)
.build(); .build()
}
pub(crate) async fn handle(&self) -> anyhow::Result<()> {
let portal = utils::normalize_server(self.args.server.as_str())?;
let gp_params = self.build_gp_params();
let prelogin = prelogin(&portal, &gp_params).await?; let prelogin = prelogin(&portal, &gp_params).await?;
let portal_credential = self.obtain_portal_credential(&prelogin).await?; let portal_credential = self.obtain_credential(&prelogin).await?;
let mut portal_config = retrieve_config(&portal, &portal_credential, &gp_params).await?; let mut portal_config = retrieve_config(&portal, &portal_credential, &gp_params).await?;
let selected_gateway = match &self.args.gateway { let selected_gateway = match &self.args.gateway {
@ -105,7 +108,14 @@ impl<'a> ConnectHandler<'a> {
let gateway = selected_gateway.server(); let gateway = selected_gateway.server();
let cred = portal_config.auth_cookie().into(); let cred = portal_config.auth_cookie().into();
let token = gateway_login(gateway, &cred, &gp_params).await?;
let token = match gateway_login(gateway, &cred, &gp_params).await {
Ok(token) => token,
Err(_) => {
info!("Gateway login failed, retrying with prelogin");
self.gateway_login_with_prelogin(gateway).await?
}
};
let vpn = Vpn::builder(gateway, &token) let vpn = Vpn::builder(gateway, &token)
.user_agent(self.args.user_agent.clone()) .user_agent(self.args.user_agent.clone())
@ -132,7 +142,17 @@ impl<'a> ConnectHandler<'a> {
Ok(()) Ok(())
} }
async fn obtain_portal_credential(&self, prelogin: &Prelogin) -> anyhow::Result<Credential> { async fn gateway_login_with_prelogin(&self, gateway: &str) -> anyhow::Result<String> {
let mut gp_params = self.build_gp_params();
gp_params.set_is_gateway(true);
let prelogin = prelogin(gateway, &gp_params).await?;
let cred = self.obtain_credential(&prelogin).await?;
gateway_login(gateway, &cred, &gp_params).await
}
async fn obtain_credential(&self, prelogin: &Prelogin) -> anyhow::Result<Credential> {
match prelogin { match prelogin {
Prelogin::Saml(prelogin) => { Prelogin::Saml(prelogin) => {
SamlAuthLauncher::new(&self.args.server) SamlAuthLauncher::new(&self.args.server)

View File

@ -44,6 +44,7 @@ impl ClientOs {
#[derive(Debug, Serialize, Deserialize, Type, Default)] #[derive(Debug, Serialize, Deserialize, Type, Default)]
pub struct GpParams { pub struct GpParams {
is_gateway: bool,
user_agent: String, user_agent: String,
client_os: ClientOs, client_os: ClientOs,
os_version: Option<String>, os_version: Option<String>,
@ -58,6 +59,14 @@ impl GpParams {
GpParamsBuilder::new() GpParamsBuilder::new()
} }
pub(crate) fn is_gateway(&self) -> bool {
self.is_gateway
}
pub fn set_is_gateway(&mut self, is_gateway: bool) {
self.is_gateway = is_gateway;
}
pub(crate) fn user_agent(&self) -> &str { pub(crate) fn user_agent(&self) -> &str {
&self.user_agent &self.user_agent
} }
@ -103,6 +112,7 @@ impl GpParams {
} }
pub struct GpParamsBuilder { pub struct GpParamsBuilder {
is_gateway: bool,
user_agent: String, user_agent: String,
client_os: ClientOs, client_os: ClientOs,
os_version: Option<String>, os_version: Option<String>,
@ -115,6 +125,7 @@ pub struct GpParamsBuilder {
impl GpParamsBuilder { impl GpParamsBuilder {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
is_gateway: false,
user_agent: GP_USER_AGENT.to_string(), user_agent: GP_USER_AGENT.to_string(),
client_os: ClientOs::Linux, client_os: ClientOs::Linux,
os_version: Default::default(), os_version: Default::default(),
@ -125,6 +136,11 @@ impl GpParamsBuilder {
} }
} }
pub fn is_gateway(&mut self, is_gateway: bool) -> &mut Self {
self.is_gateway = is_gateway;
self
}
pub fn user_agent(&mut self, user_agent: &str) -> &mut Self { pub fn user_agent(&mut self, user_agent: &str) -> &mut Self {
self.user_agent = user_agent.to_string(); self.user_agent = user_agent.to_string();
self self
@ -162,6 +178,7 @@ impl GpParamsBuilder {
pub fn build(&self) -> GpParams { pub fn build(&self) -> GpParams {
GpParams { GpParams {
is_gateway: self.is_gateway,
user_agent: self.user_agent.clone(), user_agent: self.user_agent.clone(),
client_os: self.client_os.clone(), client_os: self.client_os.clone(),
os_version: self.os_version.clone(), os_version: self.os_version.clone(),

View File

@ -102,12 +102,6 @@ impl PortalConfig {
pub enum PortalConfigError { pub enum PortalConfigError {
#[error("Empty response, retrying can help")] #[error("Empty response, retrying can help")]
EmptyResponse, EmptyResponse,
#[error("Empty auth cookie, retrying can help")]
EmptyAuthCookie,
#[error("Invalid auth cookie, retrying can help")]
InvalidAuthCookie,
#[error("Empty gateways, retrying can help")]
EmptyGateways,
} }
pub async fn retrieve_config( pub async fn retrieve_config(
@ -139,24 +133,22 @@ pub async fn retrieve_config(
ensure!(!res_xml.is_empty(), PortalConfigError::EmptyResponse); ensure!(!res_xml.is_empty(), PortalConfigError::EmptyResponse);
let doc = Document::parse(&res_xml)?; let doc = Document::parse(&res_xml)?;
let gateways = parse_gateways(&doc).ok_or_else(|| anyhow::anyhow!("Failed to parse gateways"))?; let mut gateways =
parse_gateways(&doc).ok_or_else(|| anyhow::anyhow!("Failed to parse gateways"))?;
let user_auth_cookie = xml::get_child_text(&doc, "portal-userauthcookie").unwrap_or_default(); let user_auth_cookie = xml::get_child_text(&doc, "portal-userauthcookie").unwrap_or_default();
let prelogon_user_auth_cookie = let prelogon_user_auth_cookie =
xml::get_child_text(&doc, "portal-prelogonuserauthcookie").unwrap_or_default(); xml::get_child_text(&doc, "portal-prelogonuserauthcookie").unwrap_or_default();
let config_digest = xml::get_child_text(&doc, "config-digest"); let config_digest = xml::get_child_text(&doc, "config-digest");
ensure!( if gateways.is_empty() {
!user_auth_cookie.is_empty() && !prelogon_user_auth_cookie.is_empty(), gateways.push(Gateway {
PortalConfigError::EmptyAuthCookie name: server.to_string(),
); address: server.to_string(),
priority: 0,
ensure!( priority_rules: vec![],
user_auth_cookie != "empty" && prelogon_user_auth_cookie != "empty", });
PortalConfigError::InvalidAuthCookie }
);
ensure!(!gateways.is_empty(), PortalConfigError::EmptyGateways);
Ok(PortalConfig::new( Ok(PortalConfig::new(
server.to_string(), server.to_string(),

View File

@ -91,11 +91,17 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
info!("Portal prelogin, user_agent: {}", user_agent); info!("Portal prelogin, user_agent: {}", user_agent);
let portal = normalize_server(portal)?; let portal = normalize_server(portal)?;
let prelogin_url = format!("{}/global-protect/prelogin.esp", portal); let prelogin_url = format!(
"{portal}/{}/prelogin.esp",
if gp_params.is_gateway() {
"ssl-vpn"
} else {
"global-protect"
}
);
let mut params = gp_params.to_params(); let mut params = gp_params.to_params();
params.insert("tmp", "tmp"); params.insert("tmp", "tmp");
params.insert("cas-support", "yes");
if gp_params.prefer_default_browser() { if gp_params.prefer_default_browser() {
params.insert("default-browser", "1"); params.insert("default-browser", "1");
} }