mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-30 00:34:26 +01:00
Merge #3274
3274: Reject master keys that are less than 16 bytes and add `--generate-master-key` CLI option r=irevoire a=dureuill # Pull Request ## Related issue Fix #3272 Fix #3287 ## What does this PR do? ### User standpoint --- - Adds a `--generate-master-key` CLI flag to generate a fresh Master Key and exit. <img width="1351" alt="Capture d’écran 2022-12-22 à 14 18 58" src="https://user-images.githubusercontent.com/41078892/209142778-eab52eeb-eaa8-409b-897a-c0d5728c8aaa.png"> --- (relevant fragment of the `--help` message) <img width="1351" alt="Capture d’écran 2022-12-22 à 14 19 40" src="https://user-images.githubusercontent.com/41078892/209142891-ebfa2ed6-f231-4f76-a3ae-b7542c7aef04.png"> --- - When `meilisearch` is started in the `development` environment and no Master Key has been provided, then the binary prints a warning before starting. <img width="1351" alt="Capture d’écran 2022-12-22 à 14 14 49" src="https://user-images.githubusercontent.com/41078892/209142158-54eba3b7-bf71-4f3f-8840-0600b13a1a9f.png"> --- - When `meilisearch` is started in the `development` environment and the provided Master Key is shorter than 16 bytes, then the binary prints a warning before starting. <img width="1351" alt="Capture d’écran 2022-12-22 à 14 15 58" src="https://user-images.githubusercontent.com/41078892/209142295-0209fe47-c03b-424f-a73f-cee9b633137a.png"> --- - When `meilisearch` is started in the `production` environment, and no Master Key is provided, the error message is altered to generate a fresh Master Key. <img width="1351" alt="Capture d’écran 2022-12-22 à 17 29 02" src="https://user-images.githubusercontent.com/41078892/209180540-0def5798-15db-47f0-a6ec-8cfa081dea77.png"> --- - When `meilisearch` is started in the `production` environment, and the provided Master Key is shorter than 16 bytes, then the binary exits with an error. <img width="1351" alt="Capture d’écran 2022-12-22 à 17 28 47" src="https://user-images.githubusercontent.com/41078892/209180567-fa54fe33-fbc4-4b9f-b281-7dfb7b33af85.png"> --- This implements the solution B described here: https://github.com/meilisearch/product/discussions/538#discussioncomment-4391346 ### Implementation standpoint - Add a new `meilisearch-auth::generate_master_key` function that uses a Cryptographic Random Number Generator (CRNG) to fill a vector of 32 bytes before encoding these bytes as base64 ## PR checklist Please check if your PR fulfills the following requirements: - [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)? - [x] Have you read the contributing guidelines? - [x] Have you made sure that the title is accurate and descriptive of the changes? Thank you so much for contributing to Meilisearch! Co-authored-by: Louis Dureuil <louis@meilisearch.com> Co-authored-by: Tamo <tamo@meilisearch.com>
This commit is contained in:
commit
6425e06cf2
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2352,6 +2352,7 @@ dependencies = [
|
|||||||
name = "meilisearch-auth"
|
name = "meilisearch-auth"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"hmac",
|
"hmac",
|
||||||
"meilisearch-types",
|
"meilisearch-types",
|
||||||
|
@ -4,6 +4,7 @@ version = "1.0.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64 = "0.13.1"
|
||||||
enum-iterator = "1.1.3"
|
enum-iterator = "1.1.3"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
|
@ -268,3 +268,20 @@ fn generate_default_keys(store: &HeedAuthStore) -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
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;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
@ -277,6 +277,7 @@ impl From<Opt> for Infos {
|
|||||||
indexer_options,
|
indexer_options,
|
||||||
scheduler_options,
|
scheduler_options,
|
||||||
config_file_path,
|
config_file_path,
|
||||||
|
generate_master_key: _,
|
||||||
#[cfg(all(not(debug_assertions), feature = "analytics"))]
|
#[cfg(all(not(debug_assertions), feature = "analytics"))]
|
||||||
no_analytics: _,
|
no_analytics: _,
|
||||||
} = options;
|
} = options;
|
||||||
|
@ -8,7 +8,7 @@ use actix_web::HttpServer;
|
|||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use meilisearch::analytics::Analytics;
|
use meilisearch::analytics::Analytics;
|
||||||
use meilisearch::{analytics, create_app, setup_meilisearch, Opt};
|
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]
|
#[global_allocator]
|
||||||
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
@ -33,16 +33,36 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
setup(&opt)?;
|
setup(&opt)?;
|
||||||
|
|
||||||
match opt.env.as_ref() {
|
if opt.generate_master_key {
|
||||||
"production" => {
|
println!("{}", generate_master_key());
|
||||||
if opt.master_key.is_none() {
|
return Ok(());
|
||||||
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" => (),
|
("production", None) => {
|
||||||
_ => unreachable!(),
|
anyhow::bail!(
|
||||||
|
"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):
|
||||||
|
|
||||||
|
>> export MEILI_MASTER_KEY={} <<
|
||||||
|
",
|
||||||
|
generate_master_key()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// No error; continue
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let (index_scheduler, auth_controller) = setup_meilisearch(&opt)?;
|
let (index_scheduler, auth_controller) = setup_meilisearch(&opt)?;
|
||||||
@ -151,11 +171,29 @@ Anonymous telemetry:\t\"Enabled\""
|
|||||||
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
|
|
||||||
if opt.master_key.is_some() {
|
match (opt.env.as_ref(), &opt.master_key) {
|
||||||
eprintln!("A Master Key has been set. Requests to Meilisearch won't be authorized unless you provide an authentication key.");
|
("production", Some(_)) => {
|
||||||
} else {
|
eprintln!("A Master Key has been set. Requests to Meilisearch won't be authorized unless you provide an authentication key.");
|
||||||
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");
|
("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.");
|
||||||
|
eprintln!("Restart Meilisearch with the `--generate-master-key` flag to generate a secure Master Key you can use");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("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!();
|
eprintln!();
|
||||||
|
@ -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_IGNORE_DUMP_IF_DB_EXISTS: &str = "MEILI_IGNORE_DUMP_IF_DB_EXISTS";
|
||||||
const MEILI_DUMP_DIR: &str = "MEILI_DUMP_DIR";
|
const MEILI_DUMP_DIR: &str = "MEILI_DUMP_DIR";
|
||||||
const MEILI_LOG_LEVEL: &str = "MEILI_LOG_LEVEL";
|
const MEILI_LOG_LEVEL: &str = "MEILI_LOG_LEVEL";
|
||||||
|
const MEILI_GENERATE_MASTER_KEY: &str = "MEILI_GENERATE_MASTER_KEY";
|
||||||
#[cfg(feature = "metrics")]
|
#[cfg(feature = "metrics")]
|
||||||
const MEILI_ENABLE_METRICS_ROUTE: &str = "MEILI_ENABLE_METRICS_ROUTE";
|
const MEILI_ENABLE_METRICS_ROUTE: &str = "MEILI_ENABLE_METRICS_ROUTE";
|
||||||
|
|
||||||
@ -230,6 +231,13 @@ pub struct Opt {
|
|||||||
#[serde(default = "default_log_level")]
|
#[serde(default = "default_log_level")]
|
||||||
pub log_level: String,
|
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, env = MEILI_GENERATE_MASTER_KEY)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub generate_master_key: bool,
|
||||||
|
|
||||||
/// Enables Prometheus metrics and /metrics route.
|
/// Enables Prometheus metrics and /metrics route.
|
||||||
#[cfg(feature = "metrics")]
|
#[cfg(feature = "metrics")]
|
||||||
#[clap(long, env = MEILI_ENABLE_METRICS_ROUTE)]
|
#[clap(long, env = MEILI_ENABLE_METRICS_ROUTE)]
|
||||||
@ -328,6 +336,7 @@ impl Opt {
|
|||||||
ignore_missing_snapshot: _,
|
ignore_missing_snapshot: _,
|
||||||
ignore_snapshot_if_db_exists: _,
|
ignore_snapshot_if_db_exists: _,
|
||||||
import_dump: _,
|
import_dump: _,
|
||||||
|
generate_master_key: _,
|
||||||
ignore_missing_dump: _,
|
ignore_missing_dump: _,
|
||||||
ignore_dump_if_db_exists: _,
|
ignore_dump_if_db_exists: _,
|
||||||
config_file_path: _,
|
config_file_path: _,
|
||||||
|
Loading…
Reference in New Issue
Block a user