From 66e18eae79d3ce20f2a50bbf17995b1838e6050f Mon Sep 17 00:00:00 2001 From: Louis Dureuil Date: Thu, 22 Dec 2022 11:55:27 +0100 Subject: [PATCH 1/6] auth: add generate_master_key function --- Cargo.lock | 1 + meilisearch-auth/Cargo.toml | 1 + meilisearch-auth/src/lib.rs | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index fb3c0daa2..db954797d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2352,6 +2352,7 @@ dependencies = [ name = "meilisearch-auth" version = "1.0.0" dependencies = [ + "base64", "enum-iterator", "hmac", "meilisearch-types", diff --git a/meilisearch-auth/Cargo.toml b/meilisearch-auth/Cargo.toml index fbddc14d0..383be69cf 100644 --- a/meilisearch-auth/Cargo.toml +++ b/meilisearch-auth/Cargo.toml @@ -4,6 +4,7 @@ version = "1.0.0" edition = "2021" [dependencies] +base64 = "0.13.1" enum-iterator = "1.1.3" hmac = "0.12.1" meilisearch-types = { path = "../meilisearch-types" } diff --git a/meilisearch-auth/src/lib.rs b/meilisearch-auth/src/lib.rs index 020a2821c..ea6bf34a0 100644 --- a/meilisearch-auth/src/lib.rs +++ b/meilisearch-auth/src/lib.rs @@ -268,3 +268,19 @@ fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { Ok(()) } + +pub const MASTER_KEY_MIN_SIZE: usize = 16; +const MASTER_KEY_GEN_SIZE: usize = 32; + +pub fn generate_master_key() -> String { + use rand::rngs::OsRng; + use rand::RngCore; + + let mut csprng = OsRng; + let mut buf = vec![0; MASTER_KEY_GEN_SIZE]; + csprng.fill_bytes(&mut buf); + + // let's encode the random bytes to base64 to make them human-readable and not too long. + // We're using the URL_SAFE alphabet that will produce keys without =, / or other unusual characters. + base64::encode_config(buf, base64::URL_SAFE_NO_PAD) +} From 57e851d8a9e7afffe4fb263e232837d2b30107a2 Mon Sep 17 00:00:00 2001 From: Louis Dureuil Date: Thu, 22 Dec 2022 11:57:44 +0100 Subject: [PATCH 2/6] Check for key length --- meilisearch/src/main.rs | 63 +++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/meilisearch/src/main.rs b/meilisearch/src/main.rs index 50575cbbf..01aeae457 100644 --- a/meilisearch/src/main.rs +++ b/meilisearch/src/main.rs @@ -8,7 +8,7 @@ use actix_web::HttpServer; use index_scheduler::IndexScheduler; use meilisearch::analytics::Analytics; use meilisearch::{analytics, create_app, setup_meilisearch, Opt}; -use meilisearch_auth::AuthController; +use meilisearch_auth::{generate_master_key, AuthController, MASTER_KEY_MIN_SIZE}; #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; @@ -33,16 +33,32 @@ async fn main() -> anyhow::Result<()> { setup(&opt)?; - match opt.env.as_ref() { - "production" => { - if opt.master_key.is_none() { - anyhow::bail!( - "In production mode, the environment variable MEILI_MASTER_KEY is mandatory" - ) - } + + match (opt.env.as_ref(), &opt.master_key) { + ("production", Some(master_key)) if master_key.len() < MASTER_KEY_MIN_SIZE => { + anyhow::bail!( + "In production mode, the Master Key must be of at least {MASTER_KEY_MIN_SIZE} characters, but the provided key is only {} characters long + +We generated a secure Master Key for you (you can safely copy this token): + +>> export MEILI_MASTER_KEY={} <<", + master_key.len(), + generate_master_key(), + ) } - "development" => (), - _ => unreachable!(), + ("production", None) => { + anyhow::bail!( + "In production mode, the environment variable MEILI_MASTER_KEY is mandatory + +We generated a secure Master Key for you (you can safely copy this token): + +>> export MEILI_MASTER_KEY={} << +", + generate_master_key() + ) + } + // No error; continue + _ => (), } let (index_scheduler, auth_controller) = setup_meilisearch(&opt)?; @@ -151,11 +167,28 @@ Anonymous telemetry:\t\"Enabled\"" eprintln!(); - if opt.master_key.is_some() { - eprintln!("A Master Key has been set. Requests to Meilisearch won't be authorized unless you provide an authentication key."); - } else { - eprintln!("No master key found; The server will accept unidentified requests. \ - If you need some protection in development mode, please export a key: export MEILI_MASTER_KEY=xxx"); + match (opt.env.as_ref(), &opt.master_key) { + ("production", Some(_)) => { + eprintln!("A Master Key has been set. Requests to Meilisearch won't be authorized unless you provide an authentication key."); + } + ("development", Some(master_key)) => { + eprintln!("A Master Key has been set. Requests to Meilisearch won't be authorized unless you provide an authentication key."); + + if master_key.len() < MASTER_KEY_MIN_SIZE { + eprintln!(); + log::warn!( + "The provided Master Key is too short (< {MASTER_KEY_MIN_SIZE} characters)" + ); + eprintln!("A Master Key of at least {MASTER_KEY_MIN_SIZE} characters will be required when switching to the production environment."); + } + } + ("development", None) => { + log::warn!("No master key found; The server will accept unidentified requests"); + eprintln!("If you need some protection in development mode, please export a key:\n\nexport MEILI_MASTER_KEY={}", generate_master_key()); + eprintln!("\nA Master Key of at least {MASTER_KEY_MIN_SIZE} characters will be required when switching to the production environment."); + } + // unreachable because Opt::try_build above would have failed already if any other value had been produced + _ => unreachable!(), } eprintln!(); From 3cba476a9fd899827697f6c26d22662513259e16 Mon Sep 17 00:00:00 2001 From: Louis Dureuil Date: Thu, 22 Dec 2022 11:58:04 +0100 Subject: [PATCH 3/6] Add `--generate-master-key` CLI option --- meilisearch/src/analytics/segment_analytics.rs | 1 + meilisearch/src/main.rs | 5 +++++ meilisearch/src/option.rs | 8 ++++++++ 3 files changed, 14 insertions(+) diff --git a/meilisearch/src/analytics/segment_analytics.rs b/meilisearch/src/analytics/segment_analytics.rs index 880d34c9a..8bde71688 100644 --- a/meilisearch/src/analytics/segment_analytics.rs +++ b/meilisearch/src/analytics/segment_analytics.rs @@ -277,6 +277,7 @@ impl From for Infos { indexer_options, scheduler_options, config_file_path, + generate_master_key: _, #[cfg(all(not(debug_assertions), feature = "analytics"))] no_analytics: _, } = options; diff --git a/meilisearch/src/main.rs b/meilisearch/src/main.rs index 01aeae457..3c4e314f8 100644 --- a/meilisearch/src/main.rs +++ b/meilisearch/src/main.rs @@ -33,6 +33,10 @@ async fn main() -> anyhow::Result<()> { setup(&opt)?; + if opt.generate_master_key { + println!("{}", generate_master_key()); + return Ok(()); + } match (opt.env.as_ref(), &opt.master_key) { ("production", Some(master_key)) if master_key.len() < MASTER_KEY_MIN_SIZE => { @@ -180,6 +184,7 @@ Anonymous telemetry:\t\"Enabled\"" "The provided Master Key is too short (< {MASTER_KEY_MIN_SIZE} characters)" ); eprintln!("A Master Key of at least {MASTER_KEY_MIN_SIZE} characters will be required when switching to the production environment."); + eprintln!("Restart Meilisearch with the `--generate-master-key` flag to generate a secure Master Key you can use"); } } ("development", None) => { diff --git a/meilisearch/src/option.rs b/meilisearch/src/option.rs index 40535305b..952c33a46 100644 --- a/meilisearch/src/option.rs +++ b/meilisearch/src/option.rs @@ -230,6 +230,13 @@ pub struct Opt { #[serde(default = "default_log_level")] pub log_level: String, + /// Generates a string of characters that can be used as a Master Key and exits. + /// + /// Pass the generated Master Key using the `--master-key` argument or the `MEILI_MASTER_KEY` environment variable in a subsequent Meilisearch invocation. + #[clap(long)] + #[serde(default)] + pub generate_master_key: bool, + /// Enables Prometheus metrics and /metrics route. #[cfg(feature = "metrics")] #[clap(long, env = MEILI_ENABLE_METRICS_ROUTE)] @@ -328,6 +335,7 @@ impl Opt { ignore_missing_snapshot: _, ignore_snapshot_if_db_exists: _, import_dump: _, + generate_master_key: _, ignore_missing_dump: _, ignore_dump_if_db_exists: _, config_file_path: _, From 4b6ffe0cd1c27fa6084cb8663564fb39d632f3be Mon Sep 17 00:00:00 2001 From: Tamo Date: Mon, 2 Jan 2023 16:33:02 +0100 Subject: [PATCH 4/6] Update meilisearch-auth/src/lib.rs --- meilisearch-auth/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/meilisearch-auth/src/lib.rs b/meilisearch-auth/src/lib.rs index ea6bf34a0..659447d44 100644 --- a/meilisearch-auth/src/lib.rs +++ b/meilisearch-auth/src/lib.rs @@ -276,6 +276,7 @@ pub fn generate_master_key() -> String { use rand::rngs::OsRng; use rand::RngCore; + // We need to use a cryptographically-secure source of randomness. That's why we're using the OsRng; https://crates.io/crates/getrandom let mut csprng = OsRng; let mut buf = vec![0; MASTER_KEY_GEN_SIZE]; csprng.fill_bytes(&mut buf); From 9ba4d0f921f7876ae093cd5b3c86a6f66adda015 Mon Sep 17 00:00:00 2001 From: Tamo Date: Mon, 2 Jan 2023 16:43:23 +0100 Subject: [PATCH 5/6] update the error messages according to the spec --- meilisearch/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meilisearch/src/main.rs b/meilisearch/src/main.rs index 3c4e314f8..e3309dde8 100644 --- a/meilisearch/src/main.rs +++ b/meilisearch/src/main.rs @@ -41,7 +41,7 @@ async fn main() -> anyhow::Result<()> { match (opt.env.as_ref(), &opt.master_key) { ("production", Some(master_key)) if master_key.len() < MASTER_KEY_MIN_SIZE => { anyhow::bail!( - "In production mode, the Master Key must be of at least {MASTER_KEY_MIN_SIZE} characters, but the provided key is only {} characters long + "In production mode, the master key must be of at least {MASTER_KEY_MIN_SIZE} characters, but the provided key is only {} characters long We generated a secure Master Key for you (you can safely copy this token): @@ -52,7 +52,7 @@ We generated a secure Master Key for you (you can safely copy this token): } ("production", None) => { anyhow::bail!( - "In production mode, the environment variable MEILI_MASTER_KEY is mandatory + "In production mode, you must provide a master key to secure your instance. It can be specified via the MEILI_MASTER_KEY environment variable or the --master-key launch option. We generated a secure Master Key for you (you can safely copy this token): From 1692f58b83ec6d4e1ee9bd1d18ba6b7c7aba1cbe Mon Sep 17 00:00:00 2001 From: Tamo Date: Mon, 2 Jan 2023 16:49:35 +0100 Subject: [PATCH 6/6] slightly update the message associated with the cli parameter + accept an env variable --- meilisearch/src/option.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/meilisearch/src/option.rs b/meilisearch/src/option.rs index 952c33a46..cc8aeaf50 100644 --- a/meilisearch/src/option.rs +++ b/meilisearch/src/option.rs @@ -49,6 +49,7 @@ const MEILI_IGNORE_MISSING_DUMP: &str = "MEILI_IGNORE_MISSING_DUMP"; const MEILI_IGNORE_DUMP_IF_DB_EXISTS: &str = "MEILI_IGNORE_DUMP_IF_DB_EXISTS"; const MEILI_DUMP_DIR: &str = "MEILI_DUMP_DIR"; const MEILI_LOG_LEVEL: &str = "MEILI_LOG_LEVEL"; +const MEILI_GENERATE_MASTER_KEY: &str = "MEILI_GENERATE_MASTER_KEY"; #[cfg(feature = "metrics")] const MEILI_ENABLE_METRICS_ROUTE: &str = "MEILI_ENABLE_METRICS_ROUTE"; @@ -230,10 +231,10 @@ pub struct Opt { #[serde(default = "default_log_level")] pub log_level: String, - /// Generates a string of characters that can be used as a Master Key and exits. + /// Generates a string of characters that can be used as a master key and exits. /// - /// Pass the generated Master Key using the `--master-key` argument or the `MEILI_MASTER_KEY` environment variable in a subsequent Meilisearch invocation. - #[clap(long)] + /// Pass the generated master key using the `--master-key` argument or the `MEILI_MASTER_KEY` environment variable in a subsequent Meilisearch invocation. + #[clap(long, env = MEILI_GENERATE_MASTER_KEY)] #[serde(default)] pub generate_master_key: bool,