1use std::collections::BTreeSet;
11use std::sync::Arc;
12
13use mz_catalog::memory::error::ErrorKind;
14use mz_catalog::memory::objects::{CatalogItem, Secret};
15use mz_expr::{Eval, MirScalarExpr};
16use mz_ore::collections::CollectionExt;
17use mz_ore::instrument;
18use mz_repr::{CatalogItemId, Datum, RowArena};
19use mz_sql::ast::display::AstDisplay;
20use mz_sql::ast::{ConnectionOption, ConnectionOptionName, Statement, Value, WithOptionValue};
21use mz_sql::catalog::{CatalogError, ObjectType};
22use mz_sql::plan::{self, CreateSecretPlan};
23use mz_sql::session::metadata::SessionMetadata;
24use mz_ssh_util::keys::SshKeyPairSet;
25use tracing::{Instrument, Span, warn};
26
27use crate::coord::sequencer::inner::return_if_err;
28use crate::coord::{
29 AlterSecret, Coordinator, CreateSecretEnsure, CreateSecretFinish, Message, PlanValidity,
30 RotateKeysSecretEnsure, RotateKeysSecretFinish, SecretStage, StageResult, Staged,
31};
32use crate::optimize::dataflows::{EvalTime, ExprPrep, ExprPrepOneShot};
33use crate::session::Session;
34use crate::{AdapterError, AdapterNotice, ExecuteContext, ExecuteResponse, catalog};
35
36impl Staged for SecretStage {
37 type Ctx = ExecuteContext;
38
39 fn validity(&mut self) -> &mut crate::coord::PlanValidity {
40 match self {
41 SecretStage::CreateFinish(stage) => &mut stage.validity,
42 SecretStage::RotateKeysFinish(stage) => &mut stage.validity,
43 SecretStage::RotateKeysEnsure(stage) => &mut stage.validity,
44 SecretStage::CreateEnsure(stage) => &mut stage.validity,
45 SecretStage::Alter(stage) => &mut stage.validity,
46 }
47 }
48
49 async fn stage(
50 self,
51 coord: &mut Coordinator,
52 ctx: &mut ExecuteContext,
53 ) -> Result<crate::coord::StageResult<Box<Self>>, AdapterError> {
54 match self {
55 SecretStage::CreateEnsure(stage) => {
56 coord.create_secret_ensure(ctx.session(), stage).await
57 }
58 SecretStage::CreateFinish(stage) => {
59 coord.create_secret_finish(ctx.session(), stage).await
60 }
61 SecretStage::RotateKeysEnsure(stage) => coord.rotate_keys_ensure(stage),
62 SecretStage::RotateKeysFinish(stage) => {
63 coord.rotate_keys_finish(ctx.session(), stage).await
64 }
65 SecretStage::Alter(stage) => coord.alter_secret(ctx.session(), stage.plan),
66 }
67 }
68
69 fn message(self, ctx: ExecuteContext, span: tracing::Span) -> Message {
70 Message::SecretStageReady {
71 ctx,
72 span,
73 stage: self,
74 }
75 }
76
77 fn cancel_enabled(&self) -> bool {
78 false
81 }
82}
83
84impl Coordinator {
85 #[instrument]
86 pub(crate) async fn sequence_create_secret(
87 &mut self,
88 ctx: ExecuteContext,
89 plan: plan::CreateSecretPlan,
90 ) {
91 let stage = return_if_err!(self.create_secret_validate(ctx.session(), plan).await, ctx);
92 self.sequence_staged(ctx, Span::current(), stage).await;
93 }
94
95 #[instrument]
96 async fn create_secret_validate(
97 &self,
98 session: &Session,
99 plan: plan::CreateSecretPlan,
100 ) -> Result<SecretStage, AdapterError> {
101 let validity = PlanValidity::new(
103 self.catalog(),
104 BTreeSet::new(),
105 None,
106 None,
107 session.role_metadata().clone(),
108 );
109 Ok(SecretStage::CreateEnsure(CreateSecretEnsure {
110 validity,
111 plan,
112 }))
113 }
114
115 #[instrument]
116 async fn create_secret_ensure(
117 &mut self,
118 session: &Session,
119 CreateSecretEnsure { validity, mut plan }: CreateSecretEnsure,
120 ) -> Result<StageResult<Box<SecretStage>>, AdapterError> {
121 let (item_id, global_id) = self.allocate_user_id().await?;
122
123 let secrets_controller = Arc::clone(&self.secrets_controller);
124 let payload = self.extract_secret(session, &mut plan.secret.secret_as)?;
125 let span = Span::current();
126 Ok(StageResult::Handle(mz_ore::task::spawn(
127 || "create secret ensure",
128 async move {
129 secrets_controller.ensure(item_id, &payload).await?;
130 let stage = SecretStage::CreateFinish(CreateSecretFinish {
131 validity,
132 item_id,
133 global_id,
134 plan,
135 });
136 Ok(Box::new(stage))
137 }
138 .instrument(span),
139 )))
140 }
141
142 fn extract_secret(
143 &self,
144 session: &Session,
145 secret_as: &mut MirScalarExpr,
146 ) -> Result<Vec<u8>, AdapterError> {
147 let temp_storage = RowArena::new();
148 let style = ExprPrepOneShot {
149 logical_time: EvalTime::NotAvailable,
150 session,
151 catalog_state: self.catalog().state(),
152 };
153 let secret_eval_err =
161 || AdapterError::Unstructured(anyhow::anyhow!("could not evaluate secret value"));
162 style
163 .prep_scalar_expr(secret_as)
164 .map_err(|_| secret_eval_err())?;
165 let evaled = secret_as
166 .eval(&[], &temp_storage)
167 .map_err(|_| secret_eval_err())?;
168
169 if evaled == Datum::Null {
170 coord_bail!("secret value can not be null");
171 }
172
173 let payload = evaled.unwrap_bytes();
174
175 if payload.len() > 1024 * 512 {
180 coord_bail!("secrets can not be bigger than 512KiB")
181 }
182
183 if std::str::from_utf8(payload).is_err() {
193 coord_bail!("secret value must be valid UTF-8");
197 }
198
199 Ok(Vec::from(payload))
200 }
201
202 #[instrument]
203 async fn create_secret_finish(
204 &mut self,
205 session: &Session,
206 CreateSecretFinish {
207 item_id,
208 global_id,
209 plan,
210 validity: _,
211 }: CreateSecretFinish,
212 ) -> Result<StageResult<Box<SecretStage>>, AdapterError> {
213 let CreateSecretPlan {
214 name,
215 secret,
216 if_not_exists,
217 } = plan;
218 let secret = Secret {
219 create_sql: secret.create_sql,
220 global_id,
221 };
222
223 let ops = vec![catalog::Op::CreateItem {
224 id: item_id,
225 name: name.clone(),
226 item: CatalogItem::Secret(secret),
227 owner_id: *session.current_role_id(),
228 }];
229
230 let res = match self.catalog_transact(Some(session), ops).await {
231 Ok(()) => Ok(ExecuteResponse::CreatedSecret),
232 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
233 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
234 })) if if_not_exists => {
235 if let Err(e) = self.secrets_controller.delete(item_id).await {
238 warn!(
239 "Dropping newly created secrets has encountered an error: {}",
240 e
241 );
242 } else {
243 self.caching_secrets_reader.invalidate(item_id);
244 }
245 session.add_notice(AdapterNotice::ObjectAlreadyExists {
246 name: name.item,
247 ty: "secret",
248 });
249 Ok(ExecuteResponse::CreatedSecret)
250 }
251 Err(err) => {
252 if let Err(e) = self.secrets_controller.delete(item_id).await {
253 warn!(
254 "Dropping newly created secrets has encountered an error: {}",
255 e
256 );
257 } else {
258 self.caching_secrets_reader.invalidate(item_id);
259 }
260 Err(err)
261 }
262 };
263 res.map(StageResult::Response)
264 }
265
266 #[instrument]
267 pub(crate) async fn sequence_alter_secret(
268 &mut self,
269 ctx: ExecuteContext,
270 plan: plan::AlterSecretPlan,
271 ) {
272 let validity = PlanValidity::new(
277 self.catalog(),
278 BTreeSet::new(),
279 None,
280 None,
281 ctx.session().role_metadata().clone(),
282 );
283 let stage = SecretStage::Alter(AlterSecret { validity, plan });
284 self.sequence_staged(ctx, Span::current(), stage).await;
285 }
286
287 #[instrument]
288 fn alter_secret(
289 &self,
290 session: &Session,
291 plan: plan::AlterSecretPlan,
292 ) -> Result<StageResult<Box<SecretStage>>, AdapterError> {
293 let plan::AlterSecretPlan { id, mut secret_as } = plan;
294 let caching_secrets_reader = self.caching_secrets_reader.clone();
295 let secrets_controller = Arc::clone(&self.secrets_controller);
296 let payload = self.extract_secret(session, &mut secret_as)?;
297 let contents = std::str::from_utf8(&payload).expect("validated as UTF-8 by extract_secret");
298 self.check_secret_content_guards_of_dependents(id, contents)?;
299 let span = Span::current();
300 Ok(StageResult::HandleRetire(mz_ore::task::spawn(
301 || "alter secret ensure",
302 async move {
303 secrets_controller.ensure(id, &payload).await?;
304 caching_secrets_reader.invalidate(id);
305 Ok(ExecuteResponse::AlteredObject(ObjectType::Secret))
306 }
307 .instrument(span),
308 )))
309 }
310
311 #[instrument]
312 pub(crate) async fn sequence_rotate_keys(&mut self, ctx: ExecuteContext, id: CatalogItemId) {
313 let validity = PlanValidity::new(
328 self.catalog(),
329 BTreeSet::from_iter(std::iter::once(id)),
330 None,
331 None,
332 ctx.session().role_metadata().clone(),
333 )
334 .with_dependency_hash_check(self.catalog());
335 let stage = SecretStage::RotateKeysEnsure(RotateKeysSecretEnsure { validity, id });
336 self.sequence_staged(ctx, Span::current(), stage).await;
337 }
338
339 #[instrument]
340 fn rotate_keys_ensure(
341 &self,
342 RotateKeysSecretEnsure { validity, id }: RotateKeysSecretEnsure,
343 ) -> Result<StageResult<Box<SecretStage>>, AdapterError> {
344 let caching_secrets_reader = self.caching_secrets_reader.clone();
345 let secrets_controller = Arc::clone(&self.secrets_controller);
346 let entry = self.catalog().get_entry(&id).clone();
347 let span = Span::current();
348 Ok(StageResult::Handle(mz_ore::task::spawn(
349 || "rotate keys ensure",
350 async move {
351 let secret = secrets_controller.reader().read(id).await?;
352 let previous_key_set = SshKeyPairSet::from_bytes(&secret)?;
353 let new_key_set = previous_key_set.rotate()?;
354 secrets_controller
355 .ensure(id, &new_key_set.to_bytes())
356 .await?;
357 caching_secrets_reader.invalidate(id);
358
359 let mut to_item = entry.item;
360 match &mut to_item {
361 CatalogItem::Connection(c) => {
362 let mut stmt = match mz_sql::parse::parse(&c.create_sql)
363 .expect("invalid create sql persisted to catalog")
364 .into_element()
365 .ast
366 {
367 Statement::CreateConnection(stmt) => stmt,
368 _ => coord_bail!("internal error: persisted SQL for {id} is invalid"),
369 };
370
371 stmt.values.retain(|v| {
372 v.name != ConnectionOptionName::PublicKey1
373 && v.name != ConnectionOptionName::PublicKey2
374 });
375 stmt.values.push(ConnectionOption {
376 name: ConnectionOptionName::PublicKey1,
377 value: Some(WithOptionValue::Value(Value::String(
378 new_key_set.primary().ssh_public_key(),
379 ))),
380 });
381 stmt.values.push(ConnectionOption {
382 name: ConnectionOptionName::PublicKey2,
383 value: Some(WithOptionValue::Value(Value::String(
384 new_key_set.secondary().ssh_public_key(),
385 ))),
386 });
387
388 c.create_sql = stmt.to_ast_string_stable();
389 }
390 _ => coord_bail!(
391 "internal error: rotate keys called on non-connection object {id}"
392 ),
393 }
394
395 let ops = vec![catalog::Op::UpdateItem {
396 id,
397 name: entry.name,
398 to_item,
399 }];
400
401 fail::fail_point!("rotate_keys_between_ensure_and_finish");
406
407 let stage = SecretStage::RotateKeysFinish(RotateKeysSecretFinish { validity, ops });
408 Ok(Box::new(stage))
409 }
410 .instrument(span),
411 )))
412 }
413
414 #[instrument]
415 async fn rotate_keys_finish(
416 &mut self,
417 session: &Session,
418 RotateKeysSecretFinish { ops, validity: _ }: RotateKeysSecretFinish,
419 ) -> Result<StageResult<Box<SecretStage>>, AdapterError> {
420 self.catalog_transact(Some(session), ops).await?;
421 Ok(StageResult::Response(ExecuteResponse::AlteredObject(
422 ObjectType::Connection,
423 )))
424 }
425}