risingwave_frontend/handler/
create_secret.rs1use pgwire::pg_response::{PgResponse, StatementType};
16use prost::Message;
17use risingwave_common::license::Feature;
18use risingwave_common::secret::vault_client::{HashiCorpVaultClient, HashiCorpVaultConfig};
19use risingwave_sqlparser::ast::{CreateSecretStatement, SqlOption, Value};
20use thiserror_ext::AsReport;
21
22use crate::error::{ErrorCode, Result};
23use crate::handler::{HandlerArgs, RwPgResponse};
24use crate::{Binder, WithOptions};
25
26const SECRET_BACKEND_KEY: &str = "backend";
27
28const SECRET_BACKEND_META: &str = "meta";
29const SECRET_BACKEND_HASHICORP_VAULT: &str = "hashicorp_vault";
30
31pub async fn handle_create_secret(
32 handler_args: HandlerArgs,
33 stmt: CreateSecretStatement,
34) -> Result<RwPgResponse> {
35 Feature::SecretManagement.check_available()?;
36
37 let session = handler_args.session.clone();
38 let db_name = &session.database();
39 let (schema_name, secret_name) =
40 Binder::resolve_schema_qualified_name(db_name, &stmt.secret_name)?;
41
42 if let Err(e) = session.check_secret_name_duplicated(stmt.secret_name.clone()) {
43 return if stmt.if_not_exists {
44 Ok(PgResponse::builder(StatementType::CREATE_SECRET)
45 .notice(format!("secret \"{}\" exists, skipping", secret_name))
46 .into())
47 } else {
48 Err(e)
49 };
50 }
51 let with_options = WithOptions::try_from(stmt.with_properties.0.as_ref() as &[SqlOption])?;
52
53 if !with_options.secret_ref().is_empty() {
55 return Err(ErrorCode::InvalidParameterValue(
56 "Secret references are not allowed when creating a secret".to_owned(),
57 )
58 .into());
59 }
60
61 let secret_payload = get_secret_payload(stmt.credential, with_options).await?;
62
63 let (database_id, schema_id) = session.get_database_and_schema_id_for_create(schema_name)?;
64
65 let catalog_writer = session.catalog_writer()?;
66 catalog_writer
67 .create_secret(
68 secret_name,
69 database_id,
70 schema_id,
71 session.user_id(),
72 secret_payload,
73 )
74 .await?;
75
76 Ok(PgResponse::empty_result(StatementType::CREATE_SECRET))
77}
78
79pub fn secret_to_str(value: &Value) -> Result<String> {
80 match value {
81 Value::DoubleQuotedString(s) | Value::SingleQuotedString(s) => Ok(s.clone()),
82 _ => Err(ErrorCode::InvalidInputSyntax(
83 "secret value should be quoted by ' or \" ".to_owned(),
84 )
85 .into()),
86 }
87}
88
89pub(crate) async fn get_secret_payload(
90 credential: Value,
91 with_options: WithOptions,
92) -> Result<Vec<u8>> {
93 if let Some(backend) = with_options.get(SECRET_BACKEND_KEY) {
94 match backend.to_lowercase().as_ref() {
95 SECRET_BACKEND_META => {
96 let secret = secret_to_str(&credential)?.as_bytes().to_vec();
97 let backend = risingwave_pb::secret::Secret {
98 secret_backend: Some(risingwave_pb::secret::secret::SecretBackend::Meta(
99 risingwave_pb::secret::SecretMetaBackend { value: secret },
100 )),
101 };
102 Ok(backend.encode_to_vec())
103 }
104 SECRET_BACKEND_HASHICORP_VAULT => {
105 if credential != Value::Null {
106 return Err(ErrorCode::InvalidParameterValue(
107 "credential must be null for hashicorp_vault backend".to_owned(),
108 )
109 .into());
110 }
111
112 let mut config_map = std::collections::HashMap::new();
114 for (key, value) in with_options.iter() {
115 config_map.insert(key.clone(), value.clone());
116 }
117
118 let config: HashiCorpVaultConfig =
120 serde_json::from_value(serde_json::Value::Object(
121 config_map
122 .into_iter()
123 .map(|(k, v)| (k, serde_json::Value::String(v)))
124 .collect(),
125 ))
126 .map_err(|e| {
127 ErrorCode::InvalidParameterValue(format!(
128 "Invalid HashiCorp Vault configuration: {}",
129 e.as_report()
130 ))
131 })?;
132
133 {
134 let client = HashiCorpVaultClient::new(config.clone())?;
136 client.get_secret().await?;
137 }
138
139 let backend = risingwave_pb::secret::Secret {
140 secret_backend: Some(
141 risingwave_pb::secret::secret::SecretBackend::HashicorpVault(
142 config.to_protobuf(),
143 ),
144 ),
145 };
146 Ok(backend.encode_to_vec())
147 }
148 _ => Err(ErrorCode::InvalidParameterValue(format!(
149 "secret backend \"{}\" is not supported. Supported backends are: {}",
150 backend,
151 [SECRET_BACKEND_META, SECRET_BACKEND_HASHICORP_VAULT].join(",")
152 ))
153 .into()),
154 }
155 } else {
156 Err(ErrorCode::InvalidParameterValue(format!(
157 "secret backend is not specified in with clause. Supported backends are: {}",
158 [SECRET_BACKEND_META, SECRET_BACKEND_HASHICORP_VAULT].join(",")
159 ))
160 .into())
161 }
162}