geforkt von mirrored/vaultwarden
Merge pull request #1548 from BlackDex/admin-interface
Updated diagnostics page
Dieser Commit ist enthalten in:
Commit
5323283f98
3 geänderte Dateien mit 141 neuen und 26 gelöschten Zeilen
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
api::{ApiResult, EmptyResult, JsonResult, NumberOrString},
|
api::{ApiResult, EmptyResult, JsonResult, NumberOrString},
|
||||||
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
||||||
config::ConfigBuilder,
|
config::ConfigBuilder,
|
||||||
db::{backup_database, models::*, DbConn, DbConnType},
|
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
|
||||||
error::{Error, MapResult},
|
error::{Error, MapResult},
|
||||||
mail,
|
mail,
|
||||||
util::{format_naive_datetime_local, get_display_size, is_running_in_docker},
|
util::{format_naive_datetime_local, get_display_size, is_running_in_docker},
|
||||||
|
@ -96,6 +96,27 @@ impl<'a, 'r> FromRequest<'a, 'r> for Referer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct IpHeader(Option<String>);
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for IpHeader {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_request(req: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||||
|
if req.headers().get_one(&CONFIG.ip_header()).is_some() {
|
||||||
|
Outcome::Success(IpHeader(Some(CONFIG.ip_header())))
|
||||||
|
} else if req.headers().get_one("X-Client-IP").is_some() {
|
||||||
|
Outcome::Success(IpHeader(Some(String::from("X-Client-IP"))))
|
||||||
|
} else if req.headers().get_one("X-Real-IP").is_some() {
|
||||||
|
Outcome::Success(IpHeader(Some(String::from("X-Real-IP"))))
|
||||||
|
} else if req.headers().get_one("X-Forwarded-For").is_some() {
|
||||||
|
Outcome::Success(IpHeader(Some(String::from("X-Forwarded-For"))))
|
||||||
|
} else {
|
||||||
|
Outcome::Success(IpHeader(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Used for `Location` response headers, which must specify an absolute URI
|
/// Used for `Location` response headers, which must specify an absolute URI
|
||||||
/// (see https://tools.ietf.org/html/rfc2616#section-14.30).
|
/// (see https://tools.ietf.org/html/rfc2616#section-14.30).
|
||||||
fn admin_url(referer: Referer) -> String {
|
fn admin_url(referer: Referer) -> String {
|
||||||
|
@ -475,7 +496,7 @@ fn has_http_access() -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/diagnostics")]
|
#[get("/diagnostics")]
|
||||||
fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
|
fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult<Html<String>> {
|
||||||
use crate::util::read_file_string;
|
use crate::util::read_file_string;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
|
@ -529,6 +550,11 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
|
||||||
("-".to_string(), "-".to_string(), "-".to_string())
|
("-".to_string(), "-".to_string(), "-".to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ip_header_name = match &ip_header.0 {
|
||||||
|
Some(h) => h,
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
let diagnostics_json = json!({
|
let diagnostics_json = json!({
|
||||||
"dns_resolved": dns_resolved,
|
"dns_resolved": dns_resolved,
|
||||||
"web_vault_version": web_vault_version.version,
|
"web_vault_version": web_vault_version.version,
|
||||||
|
@ -537,8 +563,13 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
|
||||||
"latest_web_build": latest_web_build,
|
"latest_web_build": latest_web_build,
|
||||||
"running_within_docker": running_within_docker,
|
"running_within_docker": running_within_docker,
|
||||||
"has_http_access": has_http_access,
|
"has_http_access": has_http_access,
|
||||||
|
"ip_header_exists": &ip_header.0.is_some(),
|
||||||
|
"ip_header_match": ip_header_name == &CONFIG.ip_header(),
|
||||||
|
"ip_header_name": ip_header_name,
|
||||||
|
"ip_header_config": &CONFIG.ip_header(),
|
||||||
"uses_proxy": uses_proxy,
|
"uses_proxy": uses_proxy,
|
||||||
"db_type": *DB_TYPE,
|
"db_type": *DB_TYPE,
|
||||||
|
"db_version": get_sql_server_version(&conn),
|
||||||
"admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
|
"admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
|
||||||
"server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference
|
"server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference
|
||||||
});
|
});
|
||||||
|
|
|
@ -125,16 +125,34 @@ macro_rules! db_run {
|
||||||
$($(
|
$($(
|
||||||
#[cfg($db)]
|
#[cfg($db)]
|
||||||
crate::db::DbConn::$db(ref $conn) => {
|
crate::db::DbConn::$db(ref $conn) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
#[allow(unused)] use crate::db::[<__ $db _schema>]::{self as schema, *};
|
#[allow(unused)] use crate::db::[<__ $db _schema>]::{self as schema, *};
|
||||||
#[allow(unused)] use [<__ $db _model>]::*;
|
#[allow(unused)] use [<__ $db _model>]::*;
|
||||||
#[allow(unused)] use crate::db::FromDb;
|
#[allow(unused)] use crate::db::FromDb;
|
||||||
}
|
}
|
||||||
$body
|
$body
|
||||||
},
|
},
|
||||||
)+)+
|
)+)+
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Same for all dbs
|
||||||
|
( @raw $conn:ident: $body:block ) => {
|
||||||
|
db_run! { @raw $conn: sqlite, mysql, postgresql $body }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Different code for each db
|
||||||
|
( @raw $conn:ident: $( $($db:ident),+ $body:block )+ ) => {
|
||||||
|
#[allow(unused)] use diesel::prelude::*;
|
||||||
|
match $conn {
|
||||||
|
$($(
|
||||||
|
#[cfg($db)]
|
||||||
|
crate::db::DbConn::$db(ref $conn) => {
|
||||||
|
$body
|
||||||
|
},
|
||||||
|
)+)+
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,7 +162,7 @@ pub trait FromDb {
|
||||||
fn from_db(self) -> Self::Output;
|
fn from_db(self) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each struct eg. Cipher, we create a CipherDb inside a module named __$db_model (where $db is sqlite, mysql or postgresql),
|
// For each struct eg. Cipher, we create a CipherDb inside a module named __$db_model (where $db is sqlite, mysql or postgresql),
|
||||||
// to implement the Diesel traits. We also provide methods to convert between them and the basic structs. Later, that module will be auto imported when using db_run!
|
// to implement the Diesel traits. We also provide methods to convert between them and the basic structs. Later, that module will be auto imported when using db_run!
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! db_object {
|
macro_rules! db_object {
|
||||||
|
@ -154,10 +172,10 @@ macro_rules! db_object {
|
||||||
$( $( #[$field_attr:meta] )* $vis:vis $field:ident : $typ:ty ),+
|
$( $( #[$field_attr:meta] )* $vis:vis $field:ident : $typ:ty ),+
|
||||||
$(,)?
|
$(,)?
|
||||||
}
|
}
|
||||||
)+ ) => {
|
)+ ) => {
|
||||||
// Create the normal struct, without attributes
|
// Create the normal struct, without attributes
|
||||||
$( pub struct $name { $( /*$( #[$field_attr] )**/ $vis $field : $typ, )+ } )+
|
$( pub struct $name { $( /*$( #[$field_attr] )**/ $vis $field : $typ, )+ } )+
|
||||||
|
|
||||||
#[cfg(sqlite)]
|
#[cfg(sqlite)]
|
||||||
pub mod __sqlite_model { $( db_object! { @db sqlite | $( #[$attr] )* | $name | $( $( #[$field_attr] )* $field : $typ ),+ } )+ }
|
pub mod __sqlite_model { $( db_object! { @db sqlite | $( #[$attr] )* | $name | $( $( #[$field_attr] )* $field : $typ ),+ } )+ }
|
||||||
#[cfg(mysql)]
|
#[cfg(mysql)]
|
||||||
|
@ -178,7 +196,7 @@ macro_rules! db_object {
|
||||||
)+ }
|
)+ }
|
||||||
|
|
||||||
impl [<$name Db>] {
|
impl [<$name Db>] {
|
||||||
#[allow(clippy::wrong_self_convention)]
|
#[allow(clippy::wrong_self_convention)]
|
||||||
#[inline(always)] pub fn to_db(x: &super::$name) -> Self { Self { $( $field: x.$field.clone(), )+ } }
|
#[inline(always)] pub fn to_db(x: &super::$name) -> Self { Self { $( $field: x.$field.clone(), )+ } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +240,36 @@ pub fn backup_database() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
use diesel::sql_types::Text;
|
||||||
|
#[derive(QueryableByName,Debug)]
|
||||||
|
struct SqlVersion {
|
||||||
|
#[sql_type = "Text"]
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the SQL Server version
|
||||||
|
pub fn get_sql_server_version(conn: &DbConn) -> String {
|
||||||
|
db_run! {@raw conn:
|
||||||
|
postgresql, mysql {
|
||||||
|
match diesel::sql_query("SELECT version() AS version;").get_result::<SqlVersion>(conn).ok() {
|
||||||
|
Some(v) => {
|
||||||
|
v.version
|
||||||
|
},
|
||||||
|
_ => "Unknown".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite {
|
||||||
|
match diesel::sql_query("SELECT sqlite_version() AS version;").get_result::<SqlVersion>(conn).ok() {
|
||||||
|
Some(v) => {
|
||||||
|
v.version
|
||||||
|
},
|
||||||
|
_ => "Unknown".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to retrieve a single connection from the managed database pool. If
|
/// Attempts to retrieve a single connection from the managed database pool. If
|
||||||
/// no pool is currently managed, fails with an `InternalServerError` status. If
|
/// no pool is currently managed, fails with an `InternalServerError` status. If
|
||||||
/// no connections are available, fails with a `ServiceUnavailable` status.
|
/// no connections are available, fails with a `ServiceUnavailable` status.
|
||||||
|
@ -263,7 +311,7 @@ mod sqlite_migrations {
|
||||||
let connection =
|
let connection =
|
||||||
diesel::sqlite::SqliteConnection::establish(&crate::CONFIG.database_url())?;
|
diesel::sqlite::SqliteConnection::establish(&crate::CONFIG.database_url())?;
|
||||||
// Disable Foreign Key Checks during migration
|
// Disable Foreign Key Checks during migration
|
||||||
|
|
||||||
// Scoped to a connection.
|
// Scoped to a connection.
|
||||||
diesel::sql_query("PRAGMA foreign_keys = OFF")
|
diesel::sql_query("PRAGMA foreign_keys = OFF")
|
||||||
.execute(&connection)
|
.execute(&connection)
|
||||||
|
@ -314,7 +362,7 @@ mod postgresql_migrations {
|
||||||
let connection =
|
let connection =
|
||||||
diesel::pg::PgConnection::establish(&crate::CONFIG.database_url())?;
|
diesel::pg::PgConnection::establish(&crate::CONFIG.database_url())?;
|
||||||
// Disable Foreign Key Checks during migration
|
// Disable Foreign Key Checks during migration
|
||||||
|
|
||||||
// FIXME: Per https://www.postgresql.org/docs/12/sql-set-constraints.html,
|
// FIXME: Per https://www.postgresql.org/docs/12/sql-set-constraints.html,
|
||||||
// "SET CONSTRAINTS sets the behavior of constraint checking within the
|
// "SET CONSTRAINTS sets the behavior of constraint checking within the
|
||||||
// current transaction", so this setting probably won't take effect for
|
// current transaction", so this setting probably won't take effect for
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div id="diagnostics-block" class="my-3 p-3 bg-white rounded shadow">
|
<div id="diagnostics-block" class="my-3 p-3 bg-white rounded shadow">
|
||||||
<h6 class="border-bottom pb-2 mb-2">Diagnostics</h6>
|
<h6 class="border-bottom pb-2 mb-2">Diagnostics</h6>
|
||||||
|
|
||||||
<h3>Version</h3>
|
<h3>Versions</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
|
@ -35,6 +35,10 @@
|
||||||
<span id="web-latest">{{diagnostics.latest_web_build}}</span>
|
<span id="web-latest">{{diagnostics.latest_web_build}}</span>
|
||||||
</dd>
|
</dd>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
<dt class="col-sm-5">Database</dt>
|
||||||
|
<dd class="col-sm-7">
|
||||||
|
<span><b>{{diagnostics.db_type}}:</b> {{diagnostics.db_version}}</span>
|
||||||
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,35 +50,65 @@
|
||||||
<dt class="col-sm-5">Running within Docker</dt>
|
<dt class="col-sm-5">Running within Docker</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
{{#if diagnostics.running_within_docker}}
|
{{#if diagnostics.running_within_docker}}
|
||||||
<span id="running-docker" class="d-block"><b>Yes</b></span>
|
<span class="d-block"><b>Yes</b></span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.running_within_docker}}
|
{{#unless diagnostics.running_within_docker}}
|
||||||
<span id="running-docker" class="d-block"><b>No</b></span>
|
<span class="d-block"><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">Uses a proxy</dt>
|
<dt class="col-sm-5">Uses a reverse proxy</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
{{#if diagnostics.uses_proxy}}
|
{{#if diagnostics.ip_header_exists}}
|
||||||
<span id="running-docker" class="d-block"><b>Yes</b></span>
|
<span class="d-block" title="IP Header found."><b>Yes</b></span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.uses_proxy}}
|
{{#unless diagnostics.ip_header_exists}}
|
||||||
<span id="running-docker" class="d-block"><b>No</b></span>
|
<span class="d-block" title="No IP Header found."><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
|
{{!-- Only show this if the IP Header Exists --}}
|
||||||
|
{{#if diagnostics.ip_header_exists}}
|
||||||
|
<dt class="col-sm-5">IP header
|
||||||
|
{{#if diagnostics.ip_header_match}}
|
||||||
|
<span class="badge badge-success" title="IP_HEADER config seems to be valid.">Match</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#unless diagnostics.ip_header_match}}
|
||||||
|
<span class="badge badge-danger" title="IP_HEADER config seems to be invalid. IP's in the log could be invalid. Please fix.">No Match</span>
|
||||||
|
{{/unless}}
|
||||||
|
</dt>
|
||||||
|
<dd class="col-sm-7">
|
||||||
|
{{#if diagnostics.ip_header_match}}
|
||||||
|
<span class="d-block"><b>Config/Server:</b> {{ diagnostics.ip_header_name }}</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#unless diagnostics.ip_header_match}}
|
||||||
|
<span class="d-block"><b>Config:</b> {{ diagnostics.ip_header_config }}</span>
|
||||||
|
<span class="d-block"><b>Server:</b> {{ diagnostics.ip_header_name }}</span>
|
||||||
|
{{/unless}}
|
||||||
|
</dd>
|
||||||
|
{{/if}}
|
||||||
|
{{!-- End if IP Header Exists --}}
|
||||||
<dt class="col-sm-5">Internet access
|
<dt class="col-sm-5">Internet access
|
||||||
{{#if diagnostics.has_http_access}}
|
{{#if diagnostics.has_http_access}}
|
||||||
<span class="badge badge-success" id="internet-success" title="We have internet access!">Ok</span>
|
<span class="badge badge-success" title="We have internet access!">Ok</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.has_http_access}}
|
{{#unless diagnostics.has_http_access}}
|
||||||
<span class="badge badge-danger" id="internet-warning" title="There seems to be no internet access. Please fix.">Error</span>
|
<span class="badge badge-danger" title="There seems to be no internet access. Please fix.">Error</span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
{{#if diagnostics.has_http_access}}
|
{{#if diagnostics.has_http_access}}
|
||||||
<span id="running-docker" class="d-block"><b>Yes</b></span>
|
<span class="d-block"><b>Yes</b></span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless diagnostics.has_http_access}}
|
{{#unless diagnostics.has_http_access}}
|
||||||
<span id="running-docker" class="d-block"><b>No</b></span>
|
<span class="d-block"><b>No</b></span>
|
||||||
|
{{/unless}}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-sm-5">Internet access via a proxy</dt>
|
||||||
|
<dd class="col-sm-7">
|
||||||
|
{{#if diagnostics.uses_proxy}}
|
||||||
|
<span class="d-block" title="Internet access goes via a proxy (HTTPS_PROXY or HTTP_PROXY is configured)."><b>Yes</b></span>
|
||||||
|
{{/if}}
|
||||||
|
{{#unless diagnostics.uses_proxy}}
|
||||||
|
<span class="d-block" title="We have direct internet access, no outgoing proxy configured."><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-5">DNS (github.com)
|
<dt class="col-sm-5">DNS (github.com)
|
||||||
|
@ -263,16 +297,18 @@
|
||||||
supportString += "* Bitwarden_rs version: v{{ version }}\n";
|
supportString += "* Bitwarden_rs version: v{{ version }}\n";
|
||||||
supportString += "* Web-vault version: v{{ diagnostics.web_vault_version }}\n";
|
supportString += "* Web-vault version: v{{ diagnostics.web_vault_version }}\n";
|
||||||
supportString += "* Running within Docker: {{ diagnostics.running_within_docker }}\n";
|
supportString += "* Running within Docker: {{ diagnostics.running_within_docker }}\n";
|
||||||
|
supportString += "* Uses a reverse proxy: {{ diagnostics.ip_header_exists }}\n";
|
||||||
|
{{#if diagnostics.ip_header_exists}}
|
||||||
|
supportString += "* IP Header check: {{ diagnostics.ip_header_match }} ({{ diagnostics.ip_header_name }})\n";
|
||||||
|
{{/if}}
|
||||||
supportString += "* Internet access: {{ diagnostics.has_http_access }}\n";
|
supportString += "* Internet access: {{ diagnostics.has_http_access }}\n";
|
||||||
supportString += "* Uses a proxy: {{ diagnostics.uses_proxy }}\n";
|
supportString += "* Internet access via a proxy: {{ diagnostics.uses_proxy }}\n";
|
||||||
supportString += "* DNS Check: " + dnsCheck + "\n";
|
supportString += "* DNS Check: " + dnsCheck + "\n";
|
||||||
supportString += "* Time Check: " + timeCheck + "\n";
|
supportString += "* Time Check: " + timeCheck + "\n";
|
||||||
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
|
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
|
||||||
supportString += "* HTTPS Check: " + httpsCheck + "\n";
|
supportString += "* HTTPS Check: " + httpsCheck + "\n";
|
||||||
supportString += "* Database type: {{ diagnostics.db_type }}\n";
|
supportString += "* Database type: {{ diagnostics.db_type }}\n";
|
||||||
{{#case diagnostics.db_type "MySQL" "PostgreSQL"}}
|
supportString += "* Database version: {{ diagnostics.db_version }}\n";
|
||||||
supportString += "* Database version: [PLEASE PROVIDE DATABASE VERSION]\n";
|
|
||||||
{{/case}}
|
|
||||||
supportString += "* Clients used: \n";
|
supportString += "* Clients used: \n";
|
||||||
supportString += "* Reverse proxy and version: \n";
|
supportString += "* Reverse proxy and version: \n";
|
||||||
supportString += "* Other relevant information: \n";
|
supportString += "* Other relevant information: \n";
|
||||||
|
|
Laden …
In neuem Issue referenzieren