Skip to content

Commit dfe44e4

Browse files
authored
chore: migrate to new secret service impl (#20)
1 parent 99ea9f6 commit dfe44e4

File tree

8 files changed

+1450
-402
lines changed

8 files changed

+1450
-402
lines changed

Cargo.lock

Lines changed: 1254 additions & 239 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ dotenvy = "0.15.7"
1414
envconfig = "0.10.0"
1515
futures = "0.3.30"
1616
handlebars = "5.1.1"
17-
integrationos-domain = { version = "4.1.6", features = ["dummy"] }
17+
integrationos-domain = { version = "7.0.0", features = ["dummy"] }
1818
metrics = "0.21.1"
1919
metrics-exporter-prometheus = "0.12.1"
2020
mongodb = "2.8.0"
2121
reqwest = { version = "0.12.3", features = [
2222
"json",
2323
"rustls-tls",
24-
], default-features = false }
24+
] }
25+
reqwest-middleware = { version = "0.3.3", features = [
26+
"json",
27+
"rustls-tls",
28+
] }
29+
reqwest-retry = "0.6.1"
30+
reqwest-tracing = "0.5.3"
2531
serde = { version = "1.0.196", features = ["derive"] }
2632
serde_json = "1.0.113"
2733
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }

src/algebra/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
mod metrics;
22
mod parameter;
33
mod refresh;
4+
mod secrets;
45
mod storage;
56

67
pub use metrics::*;
78
pub use parameter::*;
89
pub use refresh::*;
10+
pub use secrets::*;
911
pub use storage::*;

src/algebra/refresh.rs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
use crate::{
22
algebra::StorageExt,
33
domain::{Refresh, Trigger, Unit},
4-
Metrics, ParameterExt, Refreshed,
4+
Metrics, ParameterExt, Refreshed, SecretsClient,
55
};
66
use chrono::{Duration, Utc};
77
use integrationos_domain::{
88
algebra::MongoStore,
99
api_model_config::ContentType,
10-
client::secrets_client::SecretsClient,
1110
connection_oauth_definition::{Computation, ConnectionOAuthDefinition, OAuthResponse},
1211
error::IntegrationOSError as Error,
13-
get_secret_request::GetSecretRequest,
1412
oauth_secret::OAuthSecret,
1513
ApplicationError, Connection, DefaultTemplate, InternalError, OAuth, TemplateExt,
1614
};
1715
use mongodb::bson::{self, doc};
18-
use reqwest::Client;
16+
use reqwest_middleware::ClientWithMiddleware;
1917
use serde_json::json;
2018
use std::sync::Arc;
2119
use tracing::warn;
@@ -38,7 +36,7 @@ pub async fn refresh(
3836
connections_store: Arc<MongoStore<Connection>>,
3937
secrets: Arc<SecretsClient>,
4038
oauths: Arc<MongoStore<ConnectionOAuthDefinition>>,
41-
client: Client,
39+
client: ClientWithMiddleware,
4240
metrics: Arc<Metrics>,
4341
) -> Result<Unit, Error> {
4442
let refresh_before = Utc::now();
@@ -102,7 +100,7 @@ pub async fn trigger(
102100
secrets: Arc<SecretsClient>,
103101
connections: Arc<MongoStore<Connection>>,
104102
oauths: Arc<MongoStore<ConnectionOAuthDefinition>>,
105-
client: Client,
103+
client: ClientWithMiddleware,
106104
) -> Result<Refreshed, Error> {
107105
let template = DefaultTemplate::default();
108106

@@ -135,15 +133,12 @@ pub async fn trigger(
135133
))?;
136134

137135
let secret: OAuthSecret = secrets
138-
.get_secret::<OAuthSecret>(&GetSecretRequest {
139-
id: msg.connection().secrets_service_id.clone(),
140-
buildable_id: msg.connection().ownership.client_id.clone(),
141-
})
142-
.await
143-
.map_err(|e| {
144-
warn!("Failed to get secret: {}", e);
145-
ApplicationError::not_found(format!("Failed to get secret: {}", e).as_str(), None)
146-
})?;
136+
.get_secret::<OAuthSecret>(
137+
&msg.connection().secrets_service_id,
138+
&msg.connection().ownership.client_id,
139+
&msg.connection().environment,
140+
)
141+
.await?;
147142

148143
let compute_payload = serde_json::to_value(&secret).map_err(|e| {
149144
warn!("Failed to serialize secret: {}", e);
@@ -175,6 +170,7 @@ pub async fn trigger(
175170
let request = client
176171
.post(conn_oauth_definition.configuration.refresh.uri())
177172
.headers(headers.unwrap_or_default());
173+
178174
let request = match conn_oauth_definition.configuration.refresh.content {
179175
Some(ContentType::Json) => request.json(&body).query(&query),
180176
Some(ContentType::Form) => request.form(&body).query(&query),
@@ -218,7 +214,8 @@ pub async fn trigger(
218214
let secret = secrets
219215
.create_secret(
220216
msg.connection().clone().ownership.client_id,
221-
&oauth_secret.as_json(),
217+
oauth_secret.as_json(),
218+
msg.connection().environment,
222219
)
223220
.await
224221
.map_err(|e| {
@@ -240,7 +237,7 @@ pub async fn trigger(
240237
warn!("Failed to serialize oauth: {}", e);
241238
InternalError::serialize_error("Failed to serialize oauth", None)
242239
})?,
243-
"secretsServiceId": secret.id,
240+
"secretsServiceId": secret.id(),
244241
}
245242
};
246243

src/algebra/secrets.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use crate::RefreshConfig;
2+
use integrationos_domain::{
3+
environment::Environment, event_access::EventAccess, IntegrationOSError, InternalError,
4+
MongoStore, Secret,
5+
};
6+
use mongodb::bson::doc;
7+
use reqwest_middleware::ClientWithMiddleware;
8+
use serde::{Deserialize, Serialize};
9+
use serde_json::Value;
10+
use std::sync::Arc;
11+
use tracing::warn;
12+
13+
const PRODUCTION_KEY: &str = "event_access::custom::live::default::event-inc::internal-ui";
14+
const TEST_KEY: &str = "event_access::custom::test::default::event-inc::internal-ui";
15+
const INTEGRATIONOS_SECRET_HEADER: &str = "X-INTEGRATIONOS-SECRET";
16+
17+
#[derive(Debug, Clone)]
18+
pub struct SecretsClient {
19+
get: String,
20+
create: String,
21+
client: ClientWithMiddleware,
22+
event: Arc<MongoStore<EventAccess>>,
23+
}
24+
25+
#[derive(Serialize, Deserialize)]
26+
#[serde(rename_all = "camelCase")]
27+
struct CreateSecretRequest {
28+
secret: Value,
29+
}
30+
31+
impl SecretsClient {
32+
pub fn new(
33+
config: &RefreshConfig,
34+
event: &Arc<MongoStore<EventAccess>>,
35+
client: ClientWithMiddleware,
36+
) -> Self {
37+
Self {
38+
get: config.get_secret().to_string(),
39+
create: config.create_secret().to_string(),
40+
client,
41+
event: Arc::clone(event),
42+
}
43+
}
44+
45+
pub async fn get_secret<T: for<'a> Deserialize<'a>>(
46+
&self,
47+
id: &str,
48+
buildable_id: &str,
49+
environment: &Environment,
50+
) -> Result<T, IntegrationOSError> {
51+
let key = match environment {
52+
Environment::Test | Environment::Development => TEST_KEY,
53+
Environment::Live | Environment::Production => PRODUCTION_KEY,
54+
};
55+
56+
let event = self
57+
.event
58+
.get_one(doc! {
59+
"ownership.buildableId": buildable_id,
60+
"key": key,
61+
"deleted": false
62+
})
63+
.await?
64+
.ok_or(InternalError::key_not_found("Event access not found", None))?;
65+
66+
let access_key = event.access_key.clone();
67+
68+
let uri = format!("{}/{}", self.get, id);
69+
let response = self
70+
.client
71+
.get(&uri)
72+
.header(INTEGRATIONOS_SECRET_HEADER, access_key)
73+
.send()
74+
.await
75+
.map_err(|err| {
76+
InternalError::io_err(&format!("Failed to send request: {err}"), None)
77+
})?;
78+
79+
let secret = response.json().await;
80+
81+
let secret: Secret = secret.map_err(|err| {
82+
warn!("Failed to deserialize response: {err}");
83+
InternalError::serialize_error(&format!("Failed to deserialize response: {err}"), None)
84+
})?;
85+
86+
secret.decode()
87+
}
88+
89+
pub async fn create_secret<T: Serialize + for<'a> Deserialize<'a>>(
90+
&self,
91+
buildable_id: String,
92+
secret: T,
93+
environment: Environment,
94+
) -> Result<Secret, IntegrationOSError> {
95+
let payload = CreateSecretRequest {
96+
secret: serde_json::to_value(&secret).map_err(|e| {
97+
warn!("Failed to serialize secret: {}", e);
98+
InternalError::serialize_error("Failed to serialize secret", None)
99+
})?,
100+
};
101+
102+
let key = match environment {
103+
Environment::Test | Environment::Development => TEST_KEY,
104+
Environment::Live | Environment::Production => PRODUCTION_KEY,
105+
};
106+
107+
let event = self
108+
.event
109+
.get_one(doc! {
110+
"ownership.buildableId": buildable_id,
111+
"key": key
112+
})
113+
.await?
114+
.ok_or(InternalError::key_not_found("Event access not found", None))?;
115+
116+
let access_key = event.access_key.clone();
117+
118+
let response = self
119+
.client
120+
.post(&self.create)
121+
.json(&payload)
122+
.header(INTEGRATIONOS_SECRET_HEADER, access_key)
123+
.send()
124+
.await
125+
.map_err(|err| {
126+
InternalError::io_err(&format!("Failed to send request: {err}"), None)
127+
})?;
128+
129+
response.json().await.map_err(|err| {
130+
warn!("Failed to deserialize response: {err}");
131+
InternalError::serialize_error(&format!("Failed to deserialize response: {err}"), None)
132+
})
133+
}
134+
}

src/service/configuration/mod.rs

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use envconfig::Envconfig;
22
use integrationos_domain::{
33
database::DatabaseConfig, environment::Environment, secrets::SecretsConfig,
44
};
5-
use std::collections::HashMap;
65
use std::fmt::Debug;
76

87
#[derive(Clone, Envconfig)]
@@ -19,6 +18,15 @@ pub struct RefreshConfig {
1918
timeout: u64,
2019
#[envconfig(from = "ENVIRONMENT", default = "test")]
2120
environment: Environment,
21+
#[envconfig(from = "GET_SECRET_PATH", default = "http://localhost:3005/v1/secrets")]
22+
get_secret: String,
23+
#[envconfig(
24+
from = "CREATE_SECRET_PATH",
25+
default = "http://localhost:3005/v1/secrets"
26+
)]
27+
create_secret: String,
28+
#[envconfig(from = "MAX_RETRIES", default = "3")]
29+
max_retries: u32,
2230
}
2331

2432
impl Debug for RefreshConfig {
@@ -27,6 +35,9 @@ impl Debug for RefreshConfig {
2735
writeln!(f, "SLEEP_TIMER_IN_SECONDS: {}", self.sleep_timer)?;
2836
writeln!(f, "TIMEOUT: {}", self.timeout)?;
2937
writeln!(f, "ENVIRONMENT: {}", self.environment)?;
38+
writeln!(f, "GET_SECRET_PATH: {}", self.get_secret)?;
39+
writeln!(f, "CREATE_SECRET_PATH: {}", self.create_secret)?;
40+
writeln!(f, "MAX_RETRIES: {}", self.max_retries)?;
3041
write!(f, "{}", self.database)?;
3142
write!(f, "{}", self.secrets_config)
3243
}
@@ -56,43 +67,16 @@ impl RefreshConfig {
5667
pub fn environment(&self) -> Environment {
5768
self.environment
5869
}
59-
}
60-
61-
impl From<HashMap<&str, &str>> for RefreshConfig {
62-
fn from(value: HashMap<&str, &str>) -> Self {
63-
let refresh_before = value
64-
.get("REFRESH_BEFORE_IN_MINUTES")
65-
.and_then(|value| value.parse().ok())
66-
.unwrap_or(10);
6770

68-
let sleep_timer = value
69-
.get("SLEEP_TIMER_IN_SECONDS")
70-
.and_then(|value| value.parse().ok())
71-
.unwrap_or(20);
71+
pub fn get_secret(&self) -> &str {
72+
&self.get_secret
73+
}
7274

73-
let owned = value
74-
.iter()
75-
.map(|(k, v)| (k.to_string(), v.to_string()))
76-
.collect();
77-
let database = DatabaseConfig::init_from_hashmap(&owned).unwrap_or_default();
78-
let secrets_config = SecretsConfig::init_from_hashmap(&owned).unwrap_or_default();
79-
let timeout = value
80-
.get("TIMEOUT")
81-
.and_then(|value| value.parse().ok())
82-
.unwrap_or(30);
83-
let environment = value
84-
.get("ENVIRONMENT")
85-
.unwrap_or(&"test")
86-
.parse()
87-
.expect("Failed to parse environment");
75+
pub fn create_secret(&self) -> &str {
76+
&self.create_secret
77+
}
8878

89-
Self {
90-
refresh_before,
91-
environment,
92-
sleep_timer,
93-
timeout,
94-
database,
95-
secrets_config,
96-
}
79+
pub fn max_retries(&self) -> u32 {
80+
self.max_retries
9781
}
9882
}

0 commit comments

Comments
 (0)