http tls redirect following

This commit is contained in:
Eugene Pankov 2022-09-01 20:26:54 +02:00
parent 0808837f70
commit 2c6dbd01ac
No known key found for this signature in database
GPG key ID: 5896FCBBDD1CF4F4
7 changed files with 92 additions and 23 deletions

3
Cargo.lock generated
View file

@ -4796,6 +4796,8 @@ dependencies = [
"qrcode",
"rcgen",
"sd-notify",
"sea-orm",
"serde_json",
"serde_yaml",
"time 0.3.11",
"tokio",
@ -4805,6 +4807,7 @@ dependencies = [
"warpgate-admin",
"warpgate-common",
"warpgate-core",
"warpgate-db-entities",
"warpgate-protocol-http",
"warpgate-protocol-mysql",
"warpgate-protocol-ssh",

View file

@ -170,7 +170,7 @@ impl Default for MySQLConfig {
fn default() -> Self {
MySQLConfig {
enable: false,
listen: _default_http_listen(),
listen: _default_mysql_listen(),
certificate: "".to_owned(),
key: "".to_owned(),
}
@ -225,12 +225,15 @@ pub enum ConfigProviderKind {
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct WarpgateConfigStore {
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub targets: Vec<Target>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub users: Vec<User>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub roles: Vec<Role>,
#[serde(default)]

View file

@ -1,2 +1,3 @@
pub static BUILTIN_ADMIN_TARGET_NAME: &str = "warpgate:admin";
pub static BUILTIN_ADMIN_ROLE_NAME: &str = "warpgate:admin";
pub static BUILTIN_ADMIN_USERNAME: &str = "admin";

View file

@ -101,7 +101,11 @@ fn construct_uri(req: &Request, options: &TargetHTTPOptions, websocket: bool) ->
.clone(),
);
let scheme = target_uri.scheme().context("No scheme in the URL")?;
let scheme = match options.tls.mode {
TlsMode::Disabled => &Scheme::HTTP,
TlsMode::Preferred => target_uri.scheme().context("No scheme in the URL")?,
TlsMode::Required => &Scheme::HTTPS,
};
uri = uri.scheme(scheme.clone());
#[allow(clippy::unwrap_used)]
@ -228,6 +232,23 @@ pub async fn proxy_normal_request(
if let TlsMode::Required = options.tls.mode {
client = client.https_only(true);
}
client = client.redirect(reqwest::redirect::Policy::custom({
let tls_mode = options.tls.mode.clone();
let uri = uri.clone();
move |attempt| {
if tls_mode == TlsMode::Preferred
&& uri.scheme() == Some(&Scheme::HTTP)
&& attempt.url().scheme() == "https"
{
debug!("Following HTTP->HTTPS redirect");
attempt.follow()
} else {
attempt.stop()
}
}
}));
if !options.tls.verify {
client = client.danger_accept_invalid_certs(true);
}

View file

@ -21,7 +21,9 @@ futures = "0.3"
notify = "^5.0.0-beta.1"
qrcode = "0.12"
rcgen = {version = "0.9", features = ["zeroize"]}
serde_json = "1.0"
serde_yaml = "0.8.23"
sea-orm = { version = "^0.9", default-features = false }
time = "0.3"
tokio = {version = "1.20", features = ["tracing", "signal", "macros"]}
tracing = "0.1"
@ -30,6 +32,7 @@ uuid = "1.0"
warpgate-admin = {version = "*", path = "../warpgate-admin"}
warpgate-common = {version = "*", path = "../warpgate-common"}
warpgate-core = {version = "*", path = "../warpgate-core"}
warpgate-db-entities = {version = "*", path = "../warpgate-db-entities"}
warpgate-protocol-http = {version = "*", path = "../warpgate-protocol-http"}
warpgate-protocol-mysql = {version = "*", path = "../warpgate-protocol-mysql"}
warpgate-protocol-ssh = {version = "*", path = "../warpgate-protocol-ssh"}

View file

@ -6,16 +6,18 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use dialoguer::theme::ColorfulTheme;
use rcgen::generate_simple_self_signed;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use tracing::*;
use uuid::Uuid;
use warpgate_common::helpers::fs::{secure_directory, secure_file};
use warpgate_common::helpers::hash::hash_password;
use warpgate_common::{
HTTPConfig, ListenEndpoint, MySQLConfig, SSHConfig, Secret, User, UserAuthCredential,
UserPasswordCredential, WarpgateConfigStore,
HTTPConfig, ListenEndpoint, MySQLConfig, SSHConfig, Secret, UserAuthCredential,
UserPasswordCredential, UserRequireCredentialsPolicy, WarpgateConfigStore, WarpgateError,
};
use warpgate_core::consts::BUILTIN_ADMIN_ROLE_NAME;
use warpgate_core::consts::{BUILTIN_ADMIN_ROLE_NAME, BUILTIN_ADMIN_USERNAME};
use warpgate_core::Services;
use warpgate_db_entities::{Role, User, UserRoleAssignment};
use crate::config::load_config;
@ -187,20 +189,10 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
// ---
let password = dialoguer::Password::with_theme(&theme)
let admin_password = dialoguer::Password::with_theme(&theme)
.with_prompt("Set a password for the Warpgate admin user")
.interact()?;
store.users.push(User {
id: Uuid::new_v4(),
username: "admin".into(),
credentials: vec![UserAuthCredential::Password(UserPasswordCredential {
hash: Secret::new(hash_password(&password)),
})],
credential_policy: None,
roles: vec![BUILTIN_ADMIN_ROLE_NAME.into()],
});
// ---
info!("Generated configuration:");
@ -211,10 +203,61 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
info!("Saved into {}", cli.config.display());
let config = load_config(&cli.config, true)?;
Services::new(config.clone()).await?;
let services = Services::new(config.clone()).await?;
warpgate_protocol_ssh::generate_host_keys(&config)?;
warpgate_protocol_ssh::generate_client_keys(&config)?;
{
let db = services.db.lock().await;
let admin_role = Role::Entity::find()
.filter(Role::Column::Name.eq(BUILTIN_ADMIN_ROLE_NAME))
.all(&*db)
.await?
.into_iter()
.next()
.ok_or(anyhow::anyhow!("Database inconsistent: no admin role"))?;
let admin_user = match User::Entity::find()
.filter(User::Column::Username.eq(BUILTIN_ADMIN_USERNAME))
.all(&*db)
.await?
.first()
{
Some(x) => x.to_owned(),
None => {
let values = User::ActiveModel {
id: Set(Uuid::new_v4()),
username: Set(BUILTIN_ADMIN_USERNAME.to_owned()),
credentials: Set(serde_json::to_value(vec![UserAuthCredential::Password(
UserPasswordCredential {
hash: Secret::new(hash_password(&admin_password)),
},
)])?),
credential_policy: Set(serde_json::to_value(
None::<UserRequireCredentialsPolicy>,
)?),
};
values.insert(&*db).await.map_err(WarpgateError::from)?
}
};
if UserRoleAssignment::Entity::find()
.filter(UserRoleAssignment::Column::UserId.eq(admin_user.id))
.filter(UserRoleAssignment::Column::RoleId.eq(admin_role.id))
.all(&*db)
.await?
.is_empty()
{
let values = UserRoleAssignment::ActiveModel {
user_id: Set(admin_user.id),
role_id: Set(admin_role.id),
..Default::default()
};
values.insert(&*db).await.map_err(WarpgateError::from)?;
}
}
{
info!("Generating a TLS certificate");
let cert = generate_simple_self_signed(vec![

View file

@ -32,12 +32,7 @@ pub fn load_config(path: &Path, secure: bool) -> Result<WarpgateConfig> {
paths_relative_to: path.parent().context("FS root reached")?.to_path_buf(),
};
info!(
"Using config: {path:?} (users: {}, targets: {}, roles: {})",
config.store.users.len(),
config.store.targets.len(),
config.store.roles.len(),
);
info!("Using config: {path:?}");
Ok(config)
}