1use std::collections::BTreeMap;
16use std::hash::Hash;
17use std::io::Write;
18use std::path::Path;
19use std::sync::{Arc, LazyLock, Weak};
20use std::time::Duration;
21
22use anyhow::{Context, anyhow};
23use async_nats::jetstream::consumer::DeliverPolicy;
24use async_nats::jetstream::{self};
25use aws_sdk_kinesis::Client as KinesisClient;
26use aws_sdk_kinesis::config::{AsyncSleep, SharedAsyncSleep, Sleep};
27use moka::future::Cache as MokaCache;
28use moka::ops::compute::Op;
29use phf::{Set, phf_set};
30use pulsar::authentication::oauth2::{OAuth2Authentication, OAuth2Params};
31use pulsar::{Authentication, OperationRetryOptions, Pulsar, TokioExecutor};
32use rdkafka::ClientConfig;
33use risingwave_common::bail;
34use rustls_pki_types::pem::PemObject;
35use rustls_pki_types::{CertificateDer, PrivatePkcs8KeyDer};
36use serde::Deserialize;
37use serde_with::json::JsonString;
38use serde_with::{DisplayFromStr, serde_as};
39use tempfile::NamedTempFile;
40use time::OffsetDateTime;
41use url::Url;
42use with_options::WithOptions;
43
44use crate::aws_utils::load_file_descriptor_from_s3;
45use crate::deserialize_duration_from_string;
46use crate::enforce_secret::EnforceSecret;
47use crate::error::ConnectorResult;
48use crate::sink::SinkError;
49use crate::source::nats::source::NatsOffset;
50
51pub const PRIVATE_LINK_BROKER_REWRITE_MAP_KEY: &str = "broker.rewrite.endpoints";
52pub const PRIVATE_LINK_TARGETS_KEY: &str = "privatelink.targets";
53
54const AWS_MSK_IAM_AUTH: &str = "AWS_MSK_IAM";
55
56pub const DISABLE_DEFAULT_CREDENTIAL: &str = "DISABLE_DEFAULT_CREDENTIAL";
59
60#[derive(Debug, Clone, Deserialize)]
61pub struct AwsPrivateLinkItem {
62 pub az_id: Option<String>,
63 pub port: u16,
64}
65
66use aws_config::default_provider::region::DefaultRegionChain;
67use aws_config::sts::AssumeRoleProvider;
68use aws_credential_types::provider::SharedCredentialsProvider;
69use aws_types::SdkConfig;
70use aws_types::region::Region;
71use risingwave_common::util::env_var::env_var_is_true;
72
73#[derive(Deserialize, Debug, Clone, WithOptions, PartialEq)]
75pub struct AwsAuthProps {
76 #[serde(rename = "aws.region", alias = "region", alias = "s3.region")]
77 pub region: Option<String>,
78
79 #[serde(
80 rename = "aws.endpoint_url",
81 alias = "endpoint_url",
82 alias = "endpoint",
83 alias = "s3.endpoint"
84 )]
85 pub endpoint: Option<String>,
86 #[serde(
87 rename = "aws.credentials.access_key_id",
88 alias = "access_key",
89 alias = "s3.access.key"
90 )]
91 pub access_key: Option<String>,
92 #[serde(
93 rename = "aws.credentials.secret_access_key",
94 alias = "secret_key",
95 alias = "s3.secret.key"
96 )]
97 pub secret_key: Option<String>,
98 #[serde(rename = "aws.credentials.session_token", alias = "session_token")]
99 pub session_token: Option<String>,
100 #[serde(rename = "aws.credentials.role.arn", alias = "arn")]
102 pub arn: Option<String>,
103 #[serde(rename = "aws.credentials.role.external_id", alias = "external_id")]
105 pub external_id: Option<String>,
106 #[serde(rename = "aws.profile", alias = "profile")]
107 pub profile: Option<String>,
108 #[serde(rename = "aws.msk.signer_timeout_sec")]
109 pub msk_signer_timeout_sec: Option<u64>,
110}
111
112impl EnforceSecret for AwsAuthProps {
113 const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
114 "access_key",
115 "aws.credentials.access_key_id",
116 "s3.access.key",
117 "secret_key",
118 "aws.credentials.secret_access_key",
119 "s3.secret.key",
120 "session_token",
121 "aws.credentials.session_token",
122 };
123}
124
125impl AwsAuthProps {
126 async fn build_region(&self) -> ConnectorResult<Region> {
127 if let Some(region_name) = &self.region {
128 Ok(Region::new(region_name.clone()))
129 } else {
130 let mut region_chain = DefaultRegionChain::builder();
131 if let Some(profile_name) = &self.profile {
132 region_chain = region_chain.profile_name(profile_name);
133 }
134
135 Ok(region_chain
136 .build()
137 .region()
138 .await
139 .context("region should be provided")?)
140 }
141 }
142
143 async fn build_credential_provider(&self) -> ConnectorResult<SharedCredentialsProvider> {
144 if let (Some(access_key), Some(secret_key)) =
145 (self.access_key.as_ref(), self.secret_key.as_ref())
146 {
147 Ok(SharedCredentialsProvider::new(
148 aws_credential_types::Credentials::from_keys(
149 access_key,
150 secret_key,
151 self.session_token.clone(),
152 ),
153 ))
154 } else if !env_var_is_true(DISABLE_DEFAULT_CREDENTIAL) {
155 Ok(SharedCredentialsProvider::new(
156 aws_config::default_provider::credentials::default_provider().await,
157 ))
158 } else {
159 bail!("Both \"access_key\" and \"secret_key\" are required.")
160 }
161 }
162
163 async fn with_role_provider(
164 &self,
165 credential: SharedCredentialsProvider,
166 ) -> ConnectorResult<SharedCredentialsProvider> {
167 if let Some(role_name) = &self.arn {
168 let region = self.build_region().await?;
169 let mut role = AssumeRoleProvider::builder(role_name)
170 .session_name("RisingWave")
171 .region(region);
172 if let Some(id) = &self.external_id {
173 role = role.external_id(id);
174 }
175 let provider = role.build_from_provider(credential).await;
176 Ok(SharedCredentialsProvider::new(provider))
177 } else {
178 Ok(credential)
179 }
180 }
181
182 pub async fn build_config(&self) -> ConnectorResult<SdkConfig> {
183 let region = self.build_region().await?;
184 let credentials_provider = self
185 .with_role_provider(self.build_credential_provider().await?)
186 .await?;
187 let mut config_loader = aws_config::from_env()
188 .region(region)
189 .credentials_provider(credentials_provider);
190
191 if let Some(endpoint) = self.endpoint.as_ref() {
192 config_loader = config_loader.endpoint_url(endpoint);
193 }
194
195 Ok(config_loader.load().await)
196 }
197}
198
199#[serde_as]
200#[derive(Debug, Clone, Deserialize, WithOptions, PartialEq, Hash, Eq)]
201pub struct KafkaConnectionProps {
202 #[serde(rename = "properties.bootstrap.server", alias = "kafka.brokers")]
203 pub brokers: String,
204
205 #[serde(rename = "properties.security.protocol")]
208 #[with_option(allow_alter_on_fly)]
209 security_protocol: Option<String>,
210
211 #[serde(rename = "properties.ssl.endpoint.identification.algorithm")]
212 #[with_option(allow_alter_on_fly)]
213 ssl_endpoint_identification_algorithm: Option<String>,
214
215 #[serde(rename = "properties.ssl.ca.location")]
218 #[with_option(allow_alter_on_fly)]
219 ssl_ca_location: Option<String>,
220
221 #[serde(rename = "properties.ssl.ca.pem")]
223 #[with_option(allow_alter_on_fly)]
224 ssl_ca_pem: Option<String>,
225
226 #[serde(rename = "properties.ssl.certificate.location")]
228 #[with_option(allow_alter_on_fly)]
229 ssl_certificate_location: Option<String>,
230
231 #[serde(rename = "properties.ssl.certificate.pem")]
233 #[with_option(allow_alter_on_fly)]
234 ssl_certificate_pem: Option<String>,
235
236 #[serde(rename = "properties.ssl.key.location")]
238 #[with_option(allow_alter_on_fly)]
239 ssl_key_location: Option<String>,
240
241 #[serde(rename = "properties.ssl.key.pem")]
243 #[with_option(allow_alter_on_fly)]
244 ssl_key_pem: Option<String>,
245
246 #[serde(rename = "properties.ssl.key.password")]
248 #[with_option(allow_alter_on_fly)]
249 ssl_key_password: Option<String>,
250
251 #[serde(rename = "properties.sasl.mechanism")]
253 #[with_option(allow_alter_on_fly)]
254 sasl_mechanism: Option<String>,
255
256 #[serde(rename = "properties.sasl.username")]
258 #[with_option(allow_alter_on_fly)]
259 sasl_username: Option<String>,
260
261 #[serde(rename = "properties.sasl.password")]
263 #[with_option(allow_alter_on_fly)]
264 sasl_password: Option<String>,
265
266 #[serde(rename = "properties.sasl.kerberos.service.name")]
268 sasl_kerberos_service_name: Option<String>,
269
270 #[serde(rename = "properties.sasl.kerberos.keytab")]
272 sasl_kerberos_keytab: Option<String>,
273
274 #[serde(rename = "properties.sasl.kerberos.principal")]
276 sasl_kerberos_principal: Option<String>,
277
278 #[serde(rename = "properties.sasl.kerberos.kinit.cmd")]
280 sasl_kerberos_kinit_cmd: Option<String>,
281
282 #[serde(rename = "properties.sasl.kerberos.min.time.before.relogin")]
284 sasl_kerberos_min_time_before_relogin: Option<String>,
285
286 #[serde(rename = "properties.sasl.oauthbearer.config")]
288 sasl_oathbearer_config: Option<String>,
289}
290
291impl EnforceSecret for KafkaConnectionProps {
292 const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
293 "properties.ssl.key.pem",
294 "properties.ssl.key.password",
295 "properties.sasl.password",
296 };
297}
298
299#[serde_as]
300#[derive(Debug, Clone, Deserialize, WithOptions)]
301pub struct KafkaCommon {
302 #[serde(rename = "topic", alias = "kafka.topic")]
304 pub topic: String,
305
306 #[serde(
307 rename = "properties.sync.call.timeout",
308 deserialize_with = "deserialize_duration_from_string",
309 default = "default_kafka_sync_call_timeout"
310 )]
311 #[with_option(allow_alter_on_fly)]
312 pub sync_call_timeout: Duration,
313}
314
315#[serde_as]
316#[derive(Debug, Clone, Deserialize, WithOptions, PartialEq, Hash, Eq)]
317pub struct KafkaPrivateLinkCommon {
318 #[serde(rename = "broker.rewrite.endpoints")]
320 #[serde_as(as = "Option<JsonString>")]
321 pub broker_rewrite_map: Option<BTreeMap<String, String>>,
322}
323
324const fn default_kafka_sync_call_timeout() -> Duration {
325 Duration::from_secs(5)
326}
327
328const fn default_socket_keepalive_enable() -> bool {
329 true
330}
331
332#[serde_as]
333#[derive(Debug, Clone, Deserialize, WithOptions)]
334pub struct RdKafkaPropertiesCommon {
335 #[serde(rename = "properties.message.max.bytes")]
340 #[serde_as(as = "Option<DisplayFromStr>")]
341 #[with_option(allow_alter_on_fly)]
342 pub message_max_bytes: Option<usize>,
343
344 #[serde(rename = "properties.receive.message.max.bytes")]
349 #[serde_as(as = "Option<DisplayFromStr>")]
350 #[with_option(allow_alter_on_fly)]
351 pub receive_message_max_bytes: Option<usize>,
352
353 #[serde(rename = "properties.statistics.interval.ms")]
354 #[serde_as(as = "Option<DisplayFromStr>")]
355 #[with_option(allow_alter_on_fly)]
356 pub statistics_interval_ms: Option<usize>,
357
358 #[serde(rename = "properties.client.id")]
360 #[serde_as(as = "Option<DisplayFromStr>")]
361 #[with_option(allow_alter_on_fly)]
362 pub client_id: Option<String>,
363
364 #[serde(rename = "properties.enable.ssl.certificate.verification")]
365 #[serde_as(as = "Option<DisplayFromStr>")]
366 #[with_option(allow_alter_on_fly)]
367 pub enable_ssl_certificate_verification: Option<bool>,
368
369 #[serde(rename = "properties.reconnect.backoff.ms")]
372 #[serde_as(as = "Option<DisplayFromStr>")]
373 #[with_option(allow_alter_on_fly)]
374 pub reconnect_backoff_ms: Option<usize>,
375
376 #[serde(rename = "properties.reconnect.backoff.max.ms")]
379 #[serde_as(as = "Option<DisplayFromStr>")]
380 #[with_option(allow_alter_on_fly)]
381 pub reconnect_backoff_max_ms: Option<usize>,
382
383 #[serde(rename = "properties.socket.connection.setup.timeout.ms")]
386 #[serde_as(as = "Option<DisplayFromStr>")]
387 #[with_option(allow_alter_on_fly)]
388 pub socket_connection_setup_timeout_ms: Option<usize>,
389
390 #[serde(
391 rename = "properties.socket.keepalive.enable",
392 default = "default_socket_keepalive_enable"
393 )]
394 #[serde_as(as = "DisplayFromStr")]
395 pub socket_keepalive_enable: bool,
396
397 #[serde(rename = "properties.retry.backoff.ms")]
399 #[serde_as(as = "Option<DisplayFromStr>")]
400 #[with_option(allow_alter_on_fly)]
401 pub retry_backoff_ms: Option<usize>,
402
403 #[serde(rename = "properties.retry.backoff.max.ms")]
405 #[serde_as(as = "Option<DisplayFromStr>")]
406 #[with_option(allow_alter_on_fly)]
407 pub retry_backoff_max_ms: Option<usize>,
408}
409
410impl RdKafkaPropertiesCommon {
411 pub(crate) fn set_client(&self, c: &mut rdkafka::ClientConfig) {
412 if let Some(v) = self.statistics_interval_ms {
413 c.set("statistics.interval.ms", v.to_string());
414 }
415 if let Some(v) = self.message_max_bytes {
416 c.set("message.max.bytes", v.to_string());
417 }
418 if let Some(v) = self.receive_message_max_bytes {
419 c.set("receive.message.max.bytes", v.to_string());
420 }
421 if let Some(v) = self.client_id.as_ref() {
422 c.set("client.id", v);
423 }
424 if let Some(v) = self.enable_ssl_certificate_verification {
425 c.set("enable.ssl.certificate.verification", v.to_string());
426 }
427 if let Some(v) = self.reconnect_backoff_ms {
428 c.set("reconnect.backoff.ms", v.to_string());
429 }
430 if let Some(v) = self.reconnect_backoff_max_ms {
431 c.set("reconnect.backoff.max.ms", v.to_string());
432 }
433 if let Some(v) = self.socket_connection_setup_timeout_ms {
434 c.set("socket.connection.setup.timeout.ms", v.to_string());
435 }
436 c.set(
437 "socket.keepalive.enable",
438 self.socket_keepalive_enable.to_string(),
439 );
440 if let Some(v) = self.retry_backoff_ms {
441 c.set("retry.backoff.ms", v.to_string());
442 }
443 if let Some(v) = self.retry_backoff_max_ms {
444 c.set("retry.backoff.max.ms", v.to_string());
445 }
446 }
447}
448
449impl KafkaConnectionProps {
450 #[cfg(test)]
451 pub fn test_default() -> Self {
452 Self {
453 brokers: "localhost:9092".to_owned(),
454 security_protocol: None,
455 ssl_ca_location: None,
456 ssl_certificate_location: None,
457 ssl_key_location: None,
458 ssl_ca_pem: None,
459 ssl_certificate_pem: None,
460 ssl_key_pem: None,
461 ssl_key_password: None,
462 ssl_endpoint_identification_algorithm: None,
463 sasl_mechanism: None,
464 sasl_username: None,
465 sasl_password: None,
466 sasl_kerberos_service_name: None,
467 sasl_kerberos_keytab: None,
468 sasl_kerberos_principal: None,
469 sasl_kerberos_kinit_cmd: None,
470 sasl_kerberos_min_time_before_relogin: None,
471 sasl_oathbearer_config: None,
472 }
473 }
474
475 pub(crate) fn set_security_properties(&self, config: &mut ClientConfig) {
476 if self.is_aws_msk_iam() {
478 config.set("security.protocol", "SASL_SSL");
479 config.set("sasl.mechanism", "OAUTHBEARER");
480 return;
481 }
482
483 if let Some(security_protocol) = self.security_protocol.as_ref() {
485 config.set("security.protocol", security_protocol);
486 }
487
488 if let Some(ssl_ca_location) = self.ssl_ca_location.as_ref() {
490 config.set("ssl.ca.location", ssl_ca_location);
491 }
492 if let Some(ssl_ca_pem) = self.ssl_ca_pem.as_ref() {
493 config.set("ssl.ca.pem", ssl_ca_pem);
494 }
495 if let Some(ssl_certificate_location) = self.ssl_certificate_location.as_ref() {
496 config.set("ssl.certificate.location", ssl_certificate_location);
497 }
498 if let Some(ssl_certificate_pem) = self.ssl_certificate_pem.as_ref() {
499 config.set("ssl.certificate.pem", ssl_certificate_pem);
500 }
501 if let Some(ssl_key_location) = self.ssl_key_location.as_ref() {
502 config.set("ssl.key.location", ssl_key_location);
503 }
504 if let Some(ssl_key_pem) = self.ssl_key_pem.as_ref() {
505 config.set("ssl.key.pem", ssl_key_pem);
506 }
507 if let Some(ssl_key_password) = self.ssl_key_password.as_ref() {
508 config.set("ssl.key.password", ssl_key_password);
509 }
510 if let Some(ssl_endpoint_identification_algorithm) =
511 self.ssl_endpoint_identification_algorithm.as_ref()
512 {
513 config.set(
515 "ssl.endpoint.identification.algorithm",
516 ssl_endpoint_identification_algorithm,
517 );
518 }
519
520 if let Some(sasl_mechanism) = self.sasl_mechanism.as_ref() {
522 config.set("sasl.mechanism", sasl_mechanism);
523 }
524
525 if let Some(sasl_username) = self.sasl_username.as_ref() {
527 config.set("sasl.username", sasl_username);
528 }
529 if let Some(sasl_password) = self.sasl_password.as_ref() {
530 config.set("sasl.password", sasl_password);
531 }
532
533 if let Some(sasl_kerberos_service_name) = self.sasl_kerberos_service_name.as_ref() {
535 config.set("sasl.kerberos.service.name", sasl_kerberos_service_name);
536 }
537 if let Some(sasl_kerberos_keytab) = self.sasl_kerberos_keytab.as_ref() {
538 config.set("sasl.kerberos.keytab", sasl_kerberos_keytab);
539 }
540 if let Some(sasl_kerberos_principal) = self.sasl_kerberos_principal.as_ref() {
541 config.set("sasl.kerberos.principal", sasl_kerberos_principal);
542 }
543 if let Some(sasl_kerberos_kinit_cmd) = self.sasl_kerberos_kinit_cmd.as_ref() {
544 config.set("sasl.kerberos.kinit.cmd", sasl_kerberos_kinit_cmd);
545 }
546 if let Some(sasl_kerberos_min_time_before_relogin) =
547 self.sasl_kerberos_min_time_before_relogin.as_ref()
548 {
549 config.set(
550 "sasl.kerberos.min.time.before.relogin",
551 sasl_kerberos_min_time_before_relogin,
552 );
553 }
554
555 if let Some(sasl_oathbearer_config) = self.sasl_oathbearer_config.as_ref() {
557 config.set("sasl.oauthbearer.config", sasl_oathbearer_config);
558 }
559 config.set("enable.sasl.oauthbearer.unsecure.jwt", "true");
561 }
562
563 pub(crate) fn is_aws_msk_iam(&self) -> bool {
564 if let Some(sasl_mechanism) = self.sasl_mechanism.as_ref()
565 && sasl_mechanism == AWS_MSK_IAM_AUTH
566 {
567 true
568 } else {
569 false
570 }
571 }
572}
573
574#[derive(Clone, Debug, Deserialize, WithOptions)]
575pub struct PulsarCommon {
576 #[serde(rename = "topic", alias = "pulsar.topic")]
577 pub topic: String,
578
579 #[serde(rename = "service.url", alias = "pulsar.service.url")]
580 pub service_url: String,
581
582 #[serde(rename = "auth.token")]
583 pub auth_token: Option<String>,
584}
585
586impl EnforceSecret for PulsarCommon {
587 const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
588 "pulsar.auth.token",
589 };
590}
591
592#[derive(Clone, Debug, Deserialize, WithOptions)]
593pub struct PulsarOauthCommon {
594 #[serde(rename = "oauth.issuer.url")]
595 pub issuer_url: String,
596
597 #[serde(rename = "oauth.credentials.url")]
598 pub credentials_url: String,
599
600 #[serde(rename = "oauth.audience")]
601 pub audience: String,
602
603 #[serde(rename = "oauth.scope")]
604 pub scope: Option<String>,
605}
606
607fn create_credential_temp_file(credentials: &[u8]) -> std::io::Result<NamedTempFile> {
608 let mut f = NamedTempFile::new()?;
609 f.write_all(credentials)?;
610 f.as_file().sync_all()?;
611 Ok(f)
612}
613
614impl PulsarCommon {
615 pub(crate) async fn build_client(
616 &self,
617 oauth: &Option<PulsarOauthCommon>,
618 aws_auth_props: &AwsAuthProps,
619 operation_retry_options: Option<OperationRetryOptions>,
620 ) -> ConnectorResult<Pulsar<TokioExecutor>> {
621 let mut pulsar_builder = Pulsar::builder(&self.service_url, TokioExecutor);
622 let mut _temp_file = None; if let Some(oauth) = oauth.as_ref() {
625 let (credentials_url, temp_file) = self
626 .resolve_pulsar_credentials_url(oauth, aws_auth_props)
627 .await?;
628 _temp_file = temp_file;
629
630 let auth_params = OAuth2Params {
631 issuer_url: oauth.issuer_url.clone(),
632 credentials_url,
633 audience: Some(oauth.audience.clone()),
634 scope: oauth.scope.clone(),
635 };
636
637 pulsar_builder = pulsar_builder
638 .with_auth_provider(OAuth2Authentication::client_credentials(auth_params));
639 } else if let Some(auth_token) = &self.auth_token {
640 pulsar_builder = pulsar_builder.with_auth(Authentication {
641 name: "token".to_owned(),
642 data: Vec::from(auth_token.as_str()),
643 });
644 }
645
646 if let Some(operation_retry_options) = operation_retry_options {
647 tracing::info!(
648 max_retries = ?operation_retry_options.max_retries,
649 retry_delay_ms = operation_retry_options.retry_delay.as_millis(),
650 "applying Pulsar source operation retry override"
651 );
652 pulsar_builder = pulsar_builder.with_operation_retry_options(operation_retry_options);
653 }
654
655 let res = pulsar_builder.build().await.map_err(|e| anyhow!(e))?;
656 drop(_temp_file); Ok(res)
658 }
659
660 pub(crate) async fn resolve_pulsar_credentials_url(
661 &self,
662 oauth: &PulsarOauthCommon,
663 aws_auth_props: &AwsAuthProps,
664 ) -> ConnectorResult<(String, Option<NamedTempFile>)> {
665 if let Ok(url) = Url::parse(&oauth.credentials_url) {
667 return self
668 .handle_pulsar_credentials_url(&url, aws_auth_props)
669 .await;
670 }
671
672 let path = Path::new(&oauth.credentials_url);
674 if !path.is_absolute() {
675 bail!("credentials_url must be a valid URL (s3://, file://) or an absolute file path");
676 }
677
678 if !tokio::fs::try_exists(&oauth.credentials_url)
680 .await
681 .unwrap_or(false)
682 {
683 bail!("credentials file does not exist: {}", oauth.credentials_url);
684 }
685
686 Ok((format!("file://{}", oauth.credentials_url), None))
688 }
689
690 pub(crate) async fn handle_pulsar_credentials_url(
691 &self,
692 url: &Url,
693 aws_auth_props: &AwsAuthProps,
694 ) -> ConnectorResult<(String, Option<NamedTempFile>)> {
695 match url.scheme() {
696 "s3" => {
697 let credentials = load_file_descriptor_from_s3(url, aws_auth_props).await?;
698 let temp_file = create_credential_temp_file(&credentials)
699 .context("failed to create temp file for pulsar credentials")?;
700
701 let temp_path = temp_file
702 .path()
703 .to_str()
704 .context("temp file path is not valid UTF-8")?;
705
706 Ok((format!("file://{}", temp_path), Some(temp_file)))
707 }
708 "file" => Ok((url.to_string(), None)),
709 _ => bail!(
710 "invalid credentials_url scheme '{}', only file://, s3://, and absolute file paths are supported",
711 url.scheme()
712 ),
713 }
714 }
715}
716
717#[serde_as]
718#[derive(Deserialize, Debug, Clone, WithOptions)]
719pub struct KinesisCommon {
720 #[serde(rename = "stream", alias = "kinesis.stream.name")]
721 pub stream_name: String,
722 #[serde(rename = "aws.region", alias = "kinesis.stream.region")]
723 pub stream_region: String,
724 #[serde(rename = "endpoint", alias = "kinesis.endpoint")]
725 pub endpoint: Option<String>,
726 #[serde(
727 rename = "aws.credentials.access_key_id",
728 alias = "kinesis.credentials.access"
729 )]
730 pub credentials_access_key: Option<String>,
731 #[serde(
732 rename = "aws.credentials.secret_access_key",
733 alias = "kinesis.credentials.secret"
734 )]
735 pub credentials_secret_access_key: Option<String>,
736 #[serde(
737 rename = "aws.credentials.session_token",
738 alias = "kinesis.credentials.session_token"
739 )]
740 pub session_token: Option<String>,
741 #[serde(rename = "aws.credentials.role.arn", alias = "kinesis.assumerole.arn")]
742 pub assume_role_arn: Option<String>,
743 #[serde(
744 rename = "aws.credentials.role.external_id",
745 alias = "kinesis.assumerole.external_id"
746 )]
747 pub assume_role_external_id: Option<String>,
748
749 #[serde(
751 rename = "kinesis.sdk.connect_timeout_ms",
752 default = "kinesis_default_connect_timeout_ms"
753 )]
754 #[serde_as(as = "DisplayFromStr")]
755 pub sdk_connect_timeout_ms: u64,
756
757 #[serde(
758 rename = "kinesis.sdk.read_timeout_ms",
759 default = "kinesis_default_read_timeout_ms"
760 )]
761 #[serde_as(as = "DisplayFromStr")]
762 pub sdk_read_timeout_ms: u64,
763
764 #[serde(
765 rename = "kinesis.sdk.operation_timeout_ms",
766 default = "kinesis_default_operation_timeout_ms"
767 )]
768 #[serde_as(as = "DisplayFromStr")]
769 pub sdk_operation_timeout_ms: u64,
770
771 #[serde(
772 rename = "kinesis.sdk.operation_attempt_timeout_ms",
773 default = "kinesis_default_operation_attempt_timeout_ms"
774 )]
775 #[serde_as(as = "DisplayFromStr")]
776 pub sdk_operation_attempt_timeout_ms: u64,
777
778 #[serde(
779 rename = "kinesis.sdk.max_retry_limit",
780 default = "kinesis_default_max_retry_limit"
781 )]
782 #[serde_as(as = "DisplayFromStr")]
783 pub sdk_max_retry_limit: u32,
784
785 #[serde(
786 rename = "kinesis.sdk.init_backoff_ms",
787 default = "kinesis_default_init_backoff_ms"
788 )]
789 #[serde_as(as = "DisplayFromStr")]
790 pub sdk_init_backoff_ms: u64,
791
792 #[serde(
793 rename = "kinesis.sdk.max_backoff_ms",
794 default = "kinesis_default_max_backoff_ms"
795 )]
796 #[serde_as(as = "DisplayFromStr")]
797 pub sdk_max_backoff_ms: u64,
798}
799
800#[derive(Debug)]
801pub struct KinesisAsyncSleepImpl;
802
803impl AsyncSleep for KinesisAsyncSleepImpl {
804 fn sleep(&self, duration: Duration) -> Sleep {
805 Sleep::new(async move { tokio::time::sleep(duration).await })
806 }
807}
808
809const fn kinesis_default_connect_timeout_ms() -> u64 {
810 10000
811}
812
813const fn kinesis_default_read_timeout_ms() -> u64 {
814 10000
815}
816
817const fn kinesis_default_operation_timeout_ms() -> u64 {
818 10000
819}
820
821const fn kinesis_default_operation_attempt_timeout_ms() -> u64 {
822 10000
823}
824
825const fn kinesis_default_init_backoff_ms() -> u64 {
826 1000
827}
828
829const fn kinesis_default_max_backoff_ms() -> u64 {
830 20000
831}
832
833const fn kinesis_default_max_retry_limit() -> u32 {
834 3
835}
836
837impl EnforceSecret for KinesisCommon {
838 const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
839 "kinesis.credentials.access",
840 "kinesis.credentials.secret",
841 "kinesis.credentials.session_token",
842 };
843}
844
845impl KinesisCommon {
846 pub(crate) async fn build_client(&self) -> ConnectorResult<KinesisClient> {
847 let config = AwsAuthProps {
848 region: Some(self.stream_region.clone()),
849 endpoint: self.endpoint.clone(),
850 access_key: self.credentials_access_key.clone(),
851 secret_key: self.credentials_secret_access_key.clone(),
852 session_token: self.session_token.clone(),
853 arn: self.assume_role_arn.clone(),
854 external_id: self.assume_role_external_id.clone(),
855 profile: Default::default(),
856 msk_signer_timeout_sec: Default::default(),
857 };
858 let aws_config = config.build_config().await?;
859 let mut builder = aws_sdk_kinesis::config::Builder::from(&aws_config);
860 {
861 let sleep_impl = SharedAsyncSleep::new(KinesisAsyncSleepImpl);
863 builder.set_sleep_impl(Some(sleep_impl));
864 let timeout_config = aws_smithy_types::timeout::TimeoutConfig::builder()
865 .connect_timeout(Duration::from_millis(self.sdk_connect_timeout_ms))
866 .read_timeout(Duration::from_millis(self.sdk_read_timeout_ms))
867 .operation_timeout(Duration::from_millis(self.sdk_operation_timeout_ms))
868 .operation_attempt_timeout(Duration::from_millis(
869 self.sdk_operation_attempt_timeout_ms,
870 ))
871 .build();
872 builder.set_timeout_config(Some(timeout_config));
873
874 let retry_config = aws_smithy_types::retry::RetryConfig::standard()
875 .with_initial_backoff(Duration::from_millis(self.sdk_init_backoff_ms))
876 .with_max_backoff(Duration::from_millis(self.sdk_max_backoff_ms))
877 .with_max_attempts(self.sdk_max_retry_limit);
878 builder.set_retry_config(Some(retry_config));
879 }
880 if let Some(endpoint) = &config.endpoint {
881 builder = builder.endpoint_url(endpoint);
882 }
883 Ok(KinesisClient::from_conf(builder.build()))
884 }
885}
886
887#[derive(Debug, Clone, PartialEq, Eq, Hash)]
890pub struct NatsConnectionProps {
891 pub server_url: String,
892 pub connect_mode: String,
893 pub user: Option<String>,
894 pub password: Option<String>,
895 pub jwt: Option<String>,
896 pub nkey: Option<String>,
897}
898
899pub static SHARED_NATS_CLIENT: LazyLock<MokaCache<NatsConnectionProps, Weak<async_nats::Client>>> =
907 LazyLock::new(|| MokaCache::builder().build());
908
909#[serde_as]
910#[derive(Deserialize, Debug, Clone, WithOptions)]
911pub struct NatsCommon {
912 #[serde(rename = "server_url")]
913 pub server_url: String,
914 #[serde(rename = "subject")]
915 pub subject: String,
916 #[serde(rename = "connect_mode")]
917 pub connect_mode: String,
918 #[serde(rename = "username")]
919 pub user: Option<String>,
920 #[serde(rename = "password")]
921 pub password: Option<String>,
922 #[serde(rename = "jwt")]
923 pub jwt: Option<String>,
924 #[serde(rename = "nkey")]
925 pub nkey: Option<String>,
926 #[serde(rename = "max_bytes")]
927 #[serde_as(as = "Option<DisplayFromStr>")]
928 pub max_bytes: Option<i64>,
929 #[serde(rename = "max_messages")]
930 #[serde_as(as = "Option<DisplayFromStr>")]
931 pub max_messages: Option<i64>,
932 #[serde(rename = "max_messages_per_subject")]
933 #[serde_as(as = "Option<DisplayFromStr>")]
934 pub max_messages_per_subject: Option<i64>,
935 #[serde(rename = "max_consumers")]
936 #[serde_as(as = "Option<DisplayFromStr>")]
937 pub max_consumers: Option<i32>,
938 #[serde(rename = "max_message_size")]
939 #[serde_as(as = "Option<DisplayFromStr>")]
940 pub max_message_size: Option<i32>,
941 #[serde(rename = "allow_create_stream", default)]
942 #[serde_as(as = "DisplayFromStr")]
943 pub allow_create_stream: bool,
944}
945
946impl EnforceSecret for NatsCommon {
947 const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
948 "password",
949 "jwt",
950 "nkey",
951 };
952}
953
954impl NatsCommon {
955 pub fn connection_props(&self) -> NatsConnectionProps {
957 NatsConnectionProps {
958 server_url: self.server_url.clone(),
959 connect_mode: self.connect_mode.clone(),
960 user: self.user.clone(),
961 password: self.password.clone(),
962 jwt: self.jwt.clone(),
963 nkey: self.nkey.clone(),
964 }
965 }
966
967 async fn build_client_inner(&self) -> ConnectorResult<async_nats::Client> {
969 let mut connect_options = async_nats::ConnectOptions::new();
970 match self.connect_mode.as_str() {
971 "user_and_password" => {
972 if let (Some(v_user), Some(v_password)) =
973 (self.user.as_ref(), self.password.as_ref())
974 {
975 connect_options =
976 connect_options.user_and_password(v_user.into(), v_password.into())
977 } else {
978 bail!("nats connect mode is user_and_password, but user or password is empty");
979 }
980 }
981
982 "credential" => {
983 if let (Some(v_nkey), Some(v_jwt)) = (self.nkey.as_ref(), self.jwt.as_ref()) {
984 connect_options = connect_options
985 .credentials(&self.create_credential(v_nkey, v_jwt)?)
986 .expect("failed to parse static creds")
987 } else {
988 bail!("nats connect mode is credential, but nkey or jwt is empty");
989 }
990 }
991 "plain" => {}
992 _ => {
993 bail!("nats connect mode only accepts user_and_password/credential/plain");
994 }
995 };
996
997 let servers = self.server_url.split(',').collect::<Vec<&str>>();
998 let client = connect_options
999 .connect(
1000 servers
1001 .iter()
1002 .map(|url| url.parse())
1003 .collect::<Result<Vec<async_nats::ServerAddr>, _>>()?,
1004 )
1005 .await
1006 .context("build nats client error")
1007 .map_err(SinkError::Nats)?;
1008 Ok(client)
1009 }
1010
1011 pub(crate) async fn build_client(&self) -> ConnectorResult<Arc<async_nats::Client>> {
1014 let connection_props = self.connection_props();
1015 let mut client: Option<Arc<async_nats::Client>> = None;
1016
1017 SHARED_NATS_CLIENT
1018 .entry_by_ref(&connection_props)
1019 .and_try_compute_with::<_, _, crate::error::ConnectorError>(|maybe_entry| async {
1020 if let Some(entry) = maybe_entry
1021 && let entry_value = entry.into_value()
1022 && let Some(existing_client) = entry_value.upgrade()
1023 {
1024 match existing_client.connection_state() {
1025 async_nats::connection::State::Connected => {
1026 tracing::info!("reuse existing nats client for {}", self.server_url);
1027 client = Some(existing_client);
1028 return Ok(Op::Nop);
1029 }
1030 _ => {
1031 tracing::warn!(
1032 server_url = self.server_url,
1033 "existing nats client is not connected",
1034 );
1035 }
1036 }
1037 }
1038 tracing::info!(
1039 server_url = self.server_url,
1040 "no cached client, or client disconnected, building new nats client"
1041 );
1042 let new_client = Arc::new(self.build_client_inner().await?);
1043 client = Some(new_client.clone());
1044 Ok(Op::Put(Arc::downgrade(&new_client)))
1045 })
1046 .await?;
1047
1048 Ok(client.expect("client should be set"))
1049 }
1050
1051 pub(crate) async fn build_context(&self) -> ConnectorResult<jetstream::Context> {
1052 let client = self.build_client().await?;
1053 let jetstream = async_nats::jetstream::new((*client).clone());
1054 Ok(jetstream)
1055 }
1056
1057 pub(crate) fn build_context_from_client(
1059 client: &Arc<async_nats::Client>,
1060 ) -> jetstream::Context {
1061 async_nats::jetstream::new((**client).clone())
1062 }
1063
1064 pub(crate) async fn build_consumer(
1069 &self,
1070 stream: String,
1071 durable_consumer_name: String,
1072 split_id: String,
1073 start_sequence: NatsOffset,
1074 mut config: jetstream::consumer::pull::Config,
1075 existing_client: Option<Arc<async_nats::Client>>,
1076 ) -> ConnectorResult<(
1077 async_nats::jetstream::consumer::Consumer<async_nats::jetstream::consumer::pull::Config>,
1078 Arc<async_nats::Client>,
1079 )> {
1080 let client = match existing_client {
1081 Some(c) => c,
1082 None => self.build_client().await?,
1083 };
1084 let context = Self::build_context_from_client(&client);
1085 let stream = self.build_or_get_stream(context.clone(), stream).await?;
1086 let subject_name = self
1087 .subject
1088 .replace(',', "-")
1089 .replace(['.', '>', '*', ' ', '\t'], "_");
1090 let name = format!("risingwave-consumer-{}-{}", subject_name, split_id);
1091
1092 let deliver_policy = match start_sequence {
1093 NatsOffset::Earliest => DeliverPolicy::All,
1094 NatsOffset::Latest => DeliverPolicy::New,
1095 NatsOffset::SequenceNumber(v) => {
1096 let parsed = v
1098 .parse::<u64>()
1099 .context("failed to parse nats offset as sequence number")?;
1100 DeliverPolicy::ByStartSequence {
1101 start_sequence: 1 + parsed,
1102 }
1103 }
1104 NatsOffset::Timestamp(v) => DeliverPolicy::ByStartTime {
1105 start_time: OffsetDateTime::from_unix_timestamp_nanos(v as i128 * 1_000_000)
1106 .context("invalid timestamp for nats offset")?,
1107 },
1108 NatsOffset::None => DeliverPolicy::All,
1109 };
1110
1111 let consumer = match stream.get_consumer(&name).await {
1112 Ok(consumer) => consumer,
1113 _ => {
1114 stream
1115 .get_or_create_consumer(&name, {
1116 config.deliver_policy = deliver_policy;
1117 config.durable_name = Some(durable_consumer_name);
1118 config.filter_subjects =
1119 self.subject.split(',').map(|s| s.to_owned()).collect();
1120 config
1121 })
1122 .await?
1123 }
1124 };
1125 Ok((consumer, client))
1126 }
1127
1128 pub(crate) async fn build_or_get_stream(
1129 &self,
1130 jetstream: jetstream::Context,
1131 stream_str: String,
1132 ) -> ConnectorResult<jetstream::stream::Stream> {
1133 let subjects: Vec<String> = self.subject.split(',').map(|s| s.to_owned()).collect();
1134
1135 if let Ok(mut stream_instance) = jetstream.get_stream(&stream_str).await {
1138 tracing::info!(
1139 "load existing nats stream ({:?}) with config {:?}",
1140 stream_str,
1141 stream_instance.info().await?
1142 );
1143 return Ok(stream_instance);
1144 }
1145
1146 if !self.allow_create_stream {
1147 return Err(anyhow!(
1148 "stream {} not found, set `allow_create_stream` to true to create a stream",
1149 stream_str
1150 )
1151 .into());
1152 }
1153
1154 let mut config = jetstream::stream::Config {
1155 name: stream_str.clone(),
1156 max_bytes: 1000000,
1157 subjects,
1158 ..Default::default()
1159 };
1160 if let Some(v) = self.max_bytes {
1161 config.max_bytes = v;
1162 }
1163 if let Some(v) = self.max_messages {
1164 config.max_messages = v;
1165 }
1166 if let Some(v) = self.max_messages_per_subject {
1167 config.max_messages_per_subject = v;
1168 }
1169 if let Some(v) = self.max_consumers {
1170 config.max_consumers = v;
1171 }
1172 if let Some(v) = self.max_message_size {
1173 config.max_message_size = v;
1174 }
1175 tracing::info!(
1176 "create nats stream ({:?}) with config {:?}",
1177 &stream_str,
1178 config
1179 );
1180 let stream = jetstream.get_or_create_stream(config).await?;
1181 Ok(stream)
1182 }
1183
1184 pub(crate) fn create_credential(&self, seed: &str, jwt: &str) -> ConnectorResult<String> {
1185 let creds = format!(
1186 "-----BEGIN NATS USER JWT-----\n{}\n------END NATS USER JWT------\n\n\
1187 ************************* IMPORTANT *************************\n\
1188 NKEY Seed printed below can be used to sign and prove identity.\n\
1189 NKEYs are sensitive and should be treated as secrets.\n\n\
1190 -----BEGIN USER NKEY SEED-----\n{}\n------END USER NKEY SEED------\n\n\
1191 *************************************************************",
1192 jwt, seed
1193 );
1194 Ok(creds)
1195 }
1196}
1197
1198pub(crate) fn load_certs(
1199 certificates: &str,
1200) -> ConnectorResult<Vec<rustls_pki_types::CertificateDer<'static>>> {
1201 let cert_bytes = if let Some(path) = certificates.strip_prefix("fs://") {
1202 std::fs::read_to_string(path).map(|cert| cert.as_bytes().to_owned())?
1203 } else {
1204 certificates.as_bytes().to_owned()
1205 };
1206
1207 CertificateDer::pem_slice_iter(&cert_bytes)
1208 .collect::<Result<Vec<_>, _>>()
1209 .context("Failed to parse certificates")
1210 .map_err(Into::into)
1211}
1212
1213pub(crate) fn load_private_key(
1214 certificate: &str,
1215) -> ConnectorResult<rustls_pki_types::PrivateKeyDer<'static>> {
1216 let cert_bytes = if let Some(path) = certificate.strip_prefix("fs://") {
1217 std::fs::read_to_string(path).map(|cert| cert.as_bytes().to_owned())?
1218 } else {
1219 certificate.as_bytes().to_owned()
1220 };
1221
1222 let cert = PrivatePkcs8KeyDer::pem_slice_iter(&cert_bytes)
1223 .next()
1224 .ok_or_else(|| anyhow!("No private key found"))?
1225 .context("Failed to parse private key")?;
1226 Ok(cert.into())
1227}
1228
1229#[serde_as]
1230#[derive(Deserialize, Debug, Clone, WithOptions)]
1231pub struct MongodbCommon {
1232 #[serde(rename = "mongodb.url")]
1234 pub connect_uri: String,
1235 #[serde(rename = "collection.name")]
1239 pub collection_name: String,
1240}
1241
1242impl EnforceSecret for MongodbCommon {
1243 const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
1244 "mongodb.url"
1245 };
1246}
1247
1248impl MongodbCommon {
1249 pub(crate) async fn build_client(&self) -> ConnectorResult<mongodb::Client> {
1250 let client = mongodb::Client::with_uri_str(&self.connect_uri).await?;
1251
1252 Ok(client)
1253 }
1254}