oz_keystore/hashicorp/
cloud.rs

1use reqwest::{Client, Error};
2use serde::Deserialize;
3
4pub struct HashicorpCloudClient {
5    client: Client,
6    client_id: String,
7    client_secret: String,
8    org_id: String,
9    project_id: String,
10    app_name: String,
11    api_url: String,
12    auth_url: String,
13}
14
15#[derive(Debug, Deserialize)]
16struct TokenResponse {
17    access_token: String,
18}
19
20#[derive(Debug, Deserialize)]
21pub struct StaticVersion {
22    pub value: String,
23}
24
25#[derive(Debug, Deserialize)]
26pub struct HashicorpSecret {
27    pub static_version: StaticVersion,
28}
29
30#[derive(Debug, Deserialize)]
31pub struct HashicorpResponse {
32    pub secret: HashicorpSecret,
33}
34
35impl HashicorpCloudClient {
36    pub fn new(
37        client_id: String,
38        client_secret: String,
39        org_id: String,
40        project_id: String,
41        app_name: String,
42    ) -> Self {
43        Self {
44            client: Client::new(),
45            client_id,
46            client_secret,
47            org_id,
48            project_id,
49            app_name,
50            api_url: "https://api.cloud.hashicorp.com".to_string(),
51            auth_url: "https://auth.idp.hashicorp.com".to_string(),
52        }
53    }
54
55    pub fn with_client(&self, client: Client) -> Self {
56        Self {
57            client,
58            client_id: self.client_id.clone(),
59            client_secret: self.client_secret.clone(),
60            org_id: self.org_id.clone(),
61            project_id: self.project_id.clone(),
62            app_name: self.app_name.clone(),
63            api_url: self.api_url.clone(),
64            auth_url: self.auth_url.clone(),
65        }
66    }
67
68    pub fn with_auth_base_url(&self, auth_url: impl Into<String>) -> Self {
69        Self {
70            auth_url: auth_url.into(),
71            api_url: self.api_url.clone(),
72            client: self.client.clone(),
73            client_id: self.client_id.clone(),
74            client_secret: self.client_secret.clone(),
75            org_id: self.org_id.clone(),
76            project_id: self.project_id.clone(),
77            app_name: self.app_name.clone(),
78        }
79    }
80
81    pub fn with_api_base_url(&self, api_url: impl Into<String>) -> Self {
82        Self {
83            api_url: api_url.into(),
84            auth_url: self.auth_url.clone(),
85            client: self.client.clone(),
86            client_id: self.client_id.clone(),
87            client_secret: self.client_secret.clone(),
88            org_id: self.org_id.clone(),
89            project_id: self.project_id.clone(),
90            app_name: self.app_name.clone(),
91        }
92    }
93
94    async fn get_token(&self) -> Result<String, Error> {
95        let token_response = self
96            .client
97            .post(format!("{}/oauth2/token", self.auth_url))
98            .form(&[
99                ("client_id", &self.client_id),
100                ("client_secret", &self.client_secret),
101                ("grant_type", &String::from("client_credentials")),
102                ("audience", &String::from("https://api.hashicorp.cloud")),
103            ])
104            .send()
105            .await?
106            .json::<TokenResponse>()
107            .await?;
108
109        Ok(token_response.access_token)
110    }
111
112    pub async fn get_secret(&self, secret_name: &str) -> Result<HashicorpResponse, Error> {
113        let token = self.get_token().await?;
114
115        let url = format!(
116            "{}/secrets/2023-11-28/organizations/{}/projects/{}/apps/{}/secrets/{}:open",
117            self.api_url, self.org_id, self.project_id, self.app_name, secret_name
118        );
119
120        self.client
121            .get(url)
122            .header("Authorization", format!("Bearer {}", token))
123            .send()
124            .await?
125            .json()
126            .await
127    }
128}