-
Notifications
You must be signed in to change notification settings - Fork 7
refactor: run database encryption as an operational worker #981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5d7c770
3aa7be4
69dfb34
38193f1
39335ee
c790e8c
7856caa
ee6d982
dfa2042
f85ee02
c5b8b3d
799c776
69b8a39
a84442a
20c9186
23083f9
3338bc3
9bb07b1
1babfae
9254e16
b32c58d
243b60e
48ccc9a
4ccd1fc
3c909e4
a4b5f1f
0db8d28
2c1bec6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use anyhow::{Context, Result}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use api::database_encryption::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operational_migrate, operational_scan, operational_verify, DatabaseEncryptionState, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use clap::{Parser, Subcommand}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use database::Database; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use uuid::Uuid; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[derive(Parser)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[command(about = "One-off database encryption backfill worker")] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| struct Cli { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[command(subcommand)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command: Command, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[derive(Subcommand)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enum Command { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Scan { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[arg(long, value_delimiter = ',')] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope: Vec<String>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Migrate { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[arg(long, required = true, value_delimiter = ',')] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope: Vec<String>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[arg(long, default_value_t = 500)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| batch_size: i64, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[arg(long)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_rows: Option<i64>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[arg(long)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resume: Option<Uuid>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[arg(long)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operator: String, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Verify { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[arg(long, value_delimiter = ',')] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope: Vec<String>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[tokio::main] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn main() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if run().await.is_err() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| println!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "DATABASE_ENCRYPTION_WORKER_RESULT {}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serde_json::json!({"status":"failed","error_class":"worker_failed"}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::process::exit(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn run() -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cli = Cli::parse(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let database_config = config::DatabaseConfig::from_env() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map_err(anyhow::Error::msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .context("invalid database configuration")?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let key = read_encryption_key()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let database = Database::from_config(&database_config).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let state = DatabaseEncryptionState::new(database.pool().clone(), &key)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match cli.command { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Command::Scan { scope } => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| println!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "{}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serde_json::to_string_pretty(&operational_scan(&state, scope).await?)? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print_success(None); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Command::Verify { scope } => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let report = operational_verify(&state, scope).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| println!("{}", serde_json::to_string_pretty(&report)?); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if report["pass"] != true { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anyhow::bail!("verification found plaintext or invalid envelopes"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print_success(None); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Command::Migrate { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| batch_size, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_rows, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resume, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operator, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let id = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operational_migrate(&state, scope, batch_size, max_rows, resume, &operator).await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print_success(Some(id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+76
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn print_success(job_id: Option<Uuid>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| println!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "DATABASE_ENCRYPTION_WORKER_RESULT {}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serde_json::json!({"status":"completed","job_id":job_id}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn read_encryption_key() -> Result<String> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Ok(key) = std::env::var("S3_ENCRYPTION_KEY") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 Medium · Match the API's encryption-key source precedence The API configuration prefers |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let path = std::env::var("S3_ENCRYPTION_KEY_FILE") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .context("S3_ENCRYPTION_KEY or S3_ENCRYPTION_KEY_FILE is required")?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::fs::read_to_string(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .context("failed to read S3_ENCRYPTION_KEY_FILE") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(|key| key.trim().to_string()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+98
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The key-source precedence here is inverted compared to the canonical implementation in Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
run().awaitreturns an error, the actual error message, error chain, and context are completely discarded — only a generic{"status":"failed","error_class":"worker_failed"}is printed. For a one-off worker that touches encrypted production data, the operator needs to see the real error to diagnose failures. The actualanyhow::Error(including its context chain from.context()calls throughoutoperational_migrate,run_job, etc.) should be serialized or at minimum printed to stderr before exiting.Suggestion: