risingwave_frontend/handler/
create_secret.rs1use pgwire::pg_response::{PgResponse, StatementType};
16use prost::Message;
17use risingwave_common::bail_not_implemented;
18use risingwave_common::license::Feature;
19use risingwave_sqlparser::ast::{CreateSecretStatement, SqlOption, Value};
20
21use crate::error::{ErrorCode, Result};
22use crate::handler::{HandlerArgs, RwPgResponse};
23use crate::{Binder, WithOptions};
24
25const SECRET_BACKEND_KEY: &str = "backend";
26
27const SECRET_BACKEND_META: &str = "meta";
28const SECRET_BACKEND_HASHICORP_VAULT: &str = "hashicorp_vault";
29
30pub async fn handle_create_secret(
31 handler_args: HandlerArgs,
32 stmt: CreateSecretStatement,
33) -> Result<RwPgResponse> {
34 Feature::SecretManagement
35 .check_available()
36 .map_err(|e| anyhow::anyhow!(e))?;
37
38 let session = handler_args.session.clone();
39 let db_name = &session.database();
40 let (schema_name, secret_name) =
41 Binder::resolve_schema_qualified_name(db_name, stmt.secret_name.clone())?;
42
43 if let Err(e) = session.check_secret_name_duplicated(stmt.secret_name.clone()) {
44 return if stmt.if_not_exists {
45 Ok(PgResponse::builder(StatementType::CREATE_SECRET)
46 .notice(format!("secret \"{}\" exists, skipping", secret_name))
47 .into())
48 } else {
49 Err(e)
50 };
51 }
52 let with_options = WithOptions::try_from(stmt.with_properties.0.as_ref() as &[SqlOption])?;
53
54 let secret_payload = get_secret_payload(stmt.credential, with_options)?;
55
56 let (database_id, schema_id) = session.get_database_and_schema_id_for_create(schema_name)?;
57
58 let catalog_writer = session.catalog_writer()?;
59 catalog_writer
60 .create_secret(
61 secret_name,
62 database_id,
63 schema_id,
64 session.user_id(),
65 secret_payload,
66 )
67 .await?;
68
69 Ok(PgResponse::empty_result(StatementType::CREATE_SECRET))
70}
71
72pub fn secret_to_str(value: &Value) -> Result<String> {
73 match value {
74 Value::DoubleQuotedString(s) | Value::SingleQuotedString(s) => Ok(s.to_string()),
75 _ => Err(ErrorCode::InvalidInputSyntax(
76 "secret value should be quoted by ' or \" ".to_owned(),
77 )
78 .into()),
79 }
80}
81
82pub(crate) fn get_secret_payload(credential: Value, with_options: WithOptions) -> Result<Vec<u8>> {
83 let secret = secret_to_str(&credential)?.as_bytes().to_vec();
84
85 if let Some(backend) = with_options.get(SECRET_BACKEND_KEY) {
86 match backend.to_lowercase().as_ref() {
87 SECRET_BACKEND_META => {
88 let backend = risingwave_pb::secret::Secret {
89 secret_backend: Some(risingwave_pb::secret::secret::SecretBackend::Meta(
90 risingwave_pb::secret::SecretMetaBackend { value: secret },
91 )),
92 };
93 Ok(backend.encode_to_vec())
94 }
95 SECRET_BACKEND_HASHICORP_VAULT => {
96 if credential != Value::Null {
97 return Err(ErrorCode::InvalidParameterValue(
98 "credential must be null for hashicorp_vault backend".to_owned(),
99 )
100 .into());
101 }
102 bail_not_implemented!("hashicorp_vault backend is not implemented yet")
103 }
104 _ => Err(ErrorCode::InvalidParameterValue(format!(
105 "secret backend \"{}\" is not supported. Supported backends are: {}",
106 backend,
107 [SECRET_BACKEND_META, SECRET_BACKEND_HASHICORP_VAULT].join(",")
108 ))
109 .into()),
110 }
111 } else {
112 Err(ErrorCode::InvalidParameterValue(format!(
113 "secret backend is not specified in with clause. Supported backends are: {}",
114 [SECRET_BACKEND_META, SECRET_BACKEND_HASHICORP_VAULT].join(",")
115 ))
116 .into())
117 }
118}