From 569add453d5039e4a2930be9ce0759828e056c88 Mon Sep 17 00:00:00 2001
From: Mathijs van Veluw <black.dex@gmail.com>
Date: Fri, 2 Feb 2024 21:44:19 +0100
Subject: [PATCH] Add Kubernetes environment detection (#4290)

Also check if we are running within a Kubernetes environment.
These do not always run using Docker or Podman of course.

Also renamed all the functions and variables to use `container` instead
of `docker`.
---
 src/api/admin.rs                           | 18 +++++++++---------
 src/main.rs                                | 10 +++++-----
 src/static/scripts/admin_diagnostics.js    |  4 ++--
 src/static/templates/admin/diagnostics.hbs | 10 +++++-----
 src/util.rs                                | 13 ++++++++-----
 5 files changed, 29 insertions(+), 26 deletions(-)

diff --git a/src/api/admin.rs b/src/api/admin.rs
index 4df77748..dfd3e28f 100644
--- a/src/api/admin.rs
+++ b/src/api/admin.rs
@@ -23,8 +23,8 @@ use crate::{
     error::{Error, MapResult},
     mail,
     util::{
-        docker_base_image, format_naive_datetime_local, get_display_size, get_reqwest_client, is_running_in_docker,
-        NumberOrString,
+        container_base_image, format_naive_datetime_local, get_display_size, get_reqwest_client,
+        is_running_in_container, NumberOrString,
     },
     CONFIG, VERSION,
 };
@@ -608,7 +608,7 @@ use cached::proc_macro::cached;
 /// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already.
 /// It will cache this function for 300 seconds (5 minutes) which should prevent the exhaustion of the rate limit.
 #[cached(time = 300, sync_writes = true)]
-async fn get_release_info(has_http_access: bool, running_within_docker: bool) -> (String, String, String) {
+async fn get_release_info(has_http_access: bool, running_within_container: bool) -> (String, String, String) {
     // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
     if has_http_access {
         (
@@ -625,9 +625,9 @@ async fn get_release_info(has_http_access: bool, running_within_docker: bool) ->
                 }
                 _ => "-".to_string(),
             },
-            // Do not fetch the web-vault version when running within Docker.
+            // Do not fetch the web-vault version when running within a container.
             // The web-vault version is embedded within the container it self, and should not be updated manually
-            if running_within_docker {
+            if running_within_container {
                 "-".to_string()
             } else {
                 match get_json_api::<GitRelease>(
@@ -681,7 +681,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
         };
 
     // Execute some environment checks
-    let running_within_docker = is_running_in_docker();
+    let running_within_container = is_running_in_container();
     let has_http_access = has_http_access().await;
     let uses_proxy = env::var_os("HTTP_PROXY").is_some()
         || env::var_os("http_proxy").is_some()
@@ -695,7 +695,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
     };
 
     let (latest_release, latest_commit, latest_web_build) =
-        get_release_info(has_http_access, running_within_docker).await;
+        get_release_info(has_http_access, running_within_container).await;
 
     let ip_header_name = match &ip_header.0 {
         Some(h) => h,
@@ -710,8 +710,8 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
         "web_vault_enabled": &CONFIG.web_vault_enabled(),
         "web_vault_version": web_vault_version.version.trim_start_matches('v'),
         "latest_web_build": latest_web_build,
-        "running_within_docker": running_within_docker,
-        "docker_base_image": if running_within_docker { docker_base_image() } else { "Not applicable" },
+        "running_within_container": running_within_container,
+        "container_base_image": if running_within_container { container_base_image() } else { "Not applicable" },
         "has_http_access": has_http_access,
         "ip_header_exists": &ip_header.0.is_some(),
         "ip_header_match": ip_header_name == CONFIG.ip_header(),
diff --git a/src/main.rs b/src/main.rs
index 6c3593df..60b6cf5f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -88,7 +88,7 @@ pub use config::CONFIG;
 pub use error::{Error, MapResult};
 use rocket::data::{Limits, ToByteUnit};
 use std::sync::Arc;
-pub use util::is_running_in_docker;
+pub use util::is_running_in_container;
 
 #[rocket::main]
 async fn main() -> Result<(), Error> {
@@ -415,7 +415,7 @@ async fn check_data_folder() {
     let path = Path::new(data_folder);
     if !path.exists() {
         error!("Data folder '{}' doesn't exist.", data_folder);
-        if is_running_in_docker() {
+        if is_running_in_container() {
             error!("Verify that your data volume is mounted at the correct location.");
         } else {
             error!("Create the data folder and try again.");
@@ -427,9 +427,9 @@ async fn check_data_folder() {
         exit(1);
     }
 
-    if is_running_in_docker()
+    if is_running_in_container()
         && std::env::var("I_REALLY_WANT_VOLATILE_STORAGE").is_err()
-        && !docker_data_folder_is_persistent(data_folder).await
+        && !container_data_folder_is_persistent(data_folder).await
     {
         error!(
             "No persistent volume!\n\
@@ -448,7 +448,7 @@ async fn check_data_folder() {
 /// A none persistent volume in either Docker or Podman is represented by a 64 alphanumerical string.
 /// If we detect this string, we will alert about not having a persistent self defined volume.
 /// This probably means that someone forgot to add `-v /path/to/vaultwarden_data/:/data`
-async fn docker_data_folder_is_persistent(data_folder: &str) -> bool {
+async fn container_data_folder_is_persistent(data_folder: &str) -> bool {
     if let Ok(mountinfo) = File::open("/proc/self/mountinfo").await {
         // Since there can only be one mountpoint to the DATA_FOLDER
         // We do a basic check for this mountpoint surrounded by a space.
diff --git a/src/static/scripts/admin_diagnostics.js b/src/static/scripts/admin_diagnostics.js
index 0b1d0622..a079c0aa 100644
--- a/src/static/scripts/admin_diagnostics.js
+++ b/src/static/scripts/admin_diagnostics.js
@@ -77,7 +77,7 @@ async function generateSupportString(event, dj) {
     supportString += `* Vaultwarden version: v${dj.current_release}\n`;
     supportString += `* Web-vault version: v${dj.web_vault_version}\n`;
     supportString += `* OS/Arch: ${dj.host_os}/${dj.host_arch}\n`;
-    supportString += `* Running within Docker: ${dj.running_within_docker} (Base: ${dj.docker_base_image})\n`;
+    supportString += `* Running within a container: ${dj.running_within_container} (Base: ${dj.container_base_image})\n`;
     supportString += "* Environment settings overridden: ";
     if (dj.overrides != "") {
         supportString += "true\n";
@@ -179,7 +179,7 @@ function initVersionCheck(dj) {
     }
     checkVersions("server", serverInstalled, serverLatest, serverLatestCommit);
 
-    if (!dj.running_within_docker) {
+    if (!dj.running_within_container) {
         const webInstalled = dj.web_vault_version;
         const webLatest = dj.latest_web_build;
         checkVersions("web", webInstalled, webLatest);
diff --git a/src/static/templates/admin/diagnostics.hbs b/src/static/templates/admin/diagnostics.hbs
index 171e6ef9..099a6740 100644
--- a/src/static/templates/admin/diagnostics.hbs
+++ b/src/static/templates/admin/diagnostics.hbs
@@ -28,7 +28,7 @@
                     <dd class="col-sm-7">
                         <span id="web-installed">{{page_data.web_vault_version}}</span>
                     </dd>
-                    {{#unless page_data.running_within_docker}}
+                    {{#unless page_data.running_within_container}}
                     <dt class="col-sm-5">Web Latest
                         <span class="badge bg-secondary d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
                     </dt>
@@ -59,12 +59,12 @@
                     <dd class="col-sm-7">
                         <span class="d-block"><b>{{ page_data.host_os }} / {{ page_data.host_arch }}</b></span>
                     </dd>
-                    <dt class="col-sm-5">Running within Docker</dt>
+                    <dt class="col-sm-5">Running within a container</dt>
                     <dd class="col-sm-7">
-                    {{#if page_data.running_within_docker}}
-                        <span class="d-block"><b>Yes (Base: {{ page_data.docker_base_image }})</b></span>
+                    {{#if page_data.running_within_container}}
+                        <span class="d-block"><b>Yes (Base: {{ page_data.container_base_image }})</b></span>
                     {{/if}}
-                    {{#unless page_data.running_within_docker}}
+                    {{#unless page_data.running_within_container}}
                         <span class="d-block"><b>No</b></span>
                     {{/unless}}
                     </dd>
diff --git a/src/util.rs b/src/util.rs
index a49682dd..0bf37959 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -531,14 +531,17 @@ pub fn parse_date(date: &str) -> NaiveDateTime {
 // Deployment environment methods
 //
 
-/// Returns true if the program is running in Docker or Podman.
-pub fn is_running_in_docker() -> bool {
-    Path::new("/.dockerenv").exists() || Path::new("/run/.containerenv").exists()
+/// Returns true if the program is running in Docker, Podman or Kubernetes.
+pub fn is_running_in_container() -> bool {
+    Path::new("/.dockerenv").exists()
+        || Path::new("/run/.containerenv").exists()
+        || Path::new("/run/secrets/kubernetes.io").exists()
+        || Path::new("/var/run/secrets/kubernetes.io").exists()
 }
 
-/// Simple check to determine on which docker base image vaultwarden is running.
+/// Simple check to determine on which container base image vaultwarden is running.
 /// We build images based upon Debian or Alpine, so these we check here.
-pub fn docker_base_image() -> &'static str {
+pub fn container_base_image() -> &'static str {
     if Path::new("/etc/debian_version").exists() {
         "Debian"
     } else if Path::new("/etc/alpine-release").exists() {