Skip to main content

hc_vault/approle/
auth.rs

1use serde::{Deserialize, Serialize};
2use std::time::SystemTime;
3use url::Url;
4
5use crate::internals;
6use crate::Auth as AuthTrait;
7use crate::Error;
8
9/// The Config for approle login
10#[derive(Clone, Serialize)]
11pub struct ApproleLogin {
12    /// The role-id for the role to use
13    pub role_id: String,
14    /// The secret-it for the role
15    pub secret_id: String,
16}
17
18#[allow(dead_code)]
19#[derive(Deserialize)]
20struct Auth {
21    /// Whether or not the auth-session is renewable
22    pub renewable: bool,
23    /// The duration for which this session is valid
24    pub lease_duration: u64,
25    /// The policies associated with this session/token
26    pub token_policies: Vec<String>,
27    /// IDK
28    pub accessor: String,
29    /// The actual Token that will also be needed/used for further
30    /// requests to vault to authenticate with this session
31    pub client_token: String,
32}
33
34#[allow(dead_code)]
35#[derive(Deserialize)]
36struct ApproleResponse {
37    /// The actual auth content
38    pub auth: Auth,
39    /// The duration for which this lease is valid
40    pub lease_duration: i64,
41    /// Whether or not this lease is renewable
42    pub renewable: bool,
43    /// The id of this lease
44    pub lease_id: String,
45}
46
47#[derive(Deserialize)]
48struct RenewAuth {
49    /// Whether or not the auth-session is renewable
50    pub renewable: bool,
51    /// The duration for which this session is valid
52    pub lease_duration: u64,
53    /// The policies associated with this session/token
54    pub policies: Vec<String>,
55    /// The actual Token that will also be needed/used for further
56    /// requests to vault to authenticate with this session
57    pub client_token: String,
58}
59
60#[derive(Deserialize)]
61struct RenewResponse {
62    /// The new auth data after renewal
63    pub auth: RenewAuth,
64}
65
66/// The Auth session for the approle backend, used by the vault client itself
67/// to authenticate using approle
68pub struct Session {
69    approle: ApproleLogin,
70
71    token: internals::TokenContainer,
72}
73
74impl AuthTrait for Session {
75    fn is_expired(&self) -> bool {
76        let start_time = self.token.get_start();
77        let current_time = SystemTime::now()
78            .duration_since(SystemTime::UNIX_EPOCH)
79            .unwrap()
80            .as_secs();
81
82        let elapsed = current_time - start_time;
83        let duration = self.token.get_duration();
84
85        elapsed >= duration
86    }
87    fn get_token(&self) -> String {
88        // Safety:
89        // This Operation is indirectly synchronized, because the validity of
90        // the session is checked before the Token is read and if the Token
91        // needs to be updated, all further operations (including reading the
92        // Token) are blocked until the Update of the Token is done.
93        // Therefore the Token is never read while it is also being modified.
94        match self.token.get_token() {
95            None => String::from(""),
96            Some(s) => s,
97        }
98    }
99    fn auth(&self, vault_url: &str) -> Result<(), Error> {
100        let mut login_url = match Url::parse(vault_url) {
101            Err(e) => {
102                return Err(Error::from(e));
103            }
104            Ok(url) => url,
105        };
106        login_url = match login_url.join("v1/auth/approle/login") {
107            Err(e) => {
108                return Err(Error::from(e));
109            }
110            Ok(u) => u,
111        };
112
113        let http_client = reqwest::blocking::Client::new();
114        let res = http_client.post(login_url).json(&self.approle).send();
115
116        let response = match res {
117            Err(e) => {
118                return Err(Error::from(e));
119            }
120            Ok(resp) => resp,
121        };
122
123        let status_code = response.status().as_u16();
124        if status_code != 200 && status_code != 204 {
125            return Err(Error::from(status_code));
126        }
127
128        let data = match response.json::<ApproleResponse>() {
129            Err(e) => Err(Error::from(e)),
130            Ok(json) => Ok(json),
131        };
132
133        let data = data.unwrap();
134
135        let token = data.auth.client_token;
136        let current_time = SystemTime::now()
137            .duration_since(SystemTime::UNIX_EPOCH)
138            .unwrap()
139            .as_secs();
140        let duration = data.auth.lease_duration;
141
142        // Safety:
143        // This is safe to do, because we are the only thread to access the
144        // token therefore updating it is safe
145        self.token.set_token(token);
146
147        self.token.set_renewable(data.auth.renewable);
148
149        // Update the Times afterwards to make sure that no thread could see
150        // these new valid times and try to read the token before the update
151        // is actually done, as these Times basically work as an indicator if
152        // the token can be accessed or not
153        self.token.set_start(current_time);
154        self.token.set_duration(duration);
155
156        Ok(())
157    }
158
159    fn is_renewable(&self) -> bool {
160        self.token.get_renewable()
161    }
162
163    fn get_total_duration(&self) -> u64 {
164        self.token.get_duration()
165    }
166
167    fn renew(&self, vault_url: &str) -> Result<(), Error> {
168        let mut renew_url = match Url::parse(vault_url) {
169            Err(e) => {
170                return Err(Error::from(e));
171            }
172            Ok(url) => url,
173        };
174        renew_url = match renew_url.join("v1/auth/token/renew-self") {
175            Err(e) => {
176                return Err(Error::from(e));
177            }
178            Ok(u) => u,
179        };
180
181        let http_client = reqwest::blocking::Client::new();
182        let res = http_client
183            .post(renew_url)
184            .header("X-Vault-Token", self.token.get_token().unwrap())
185            .send();
186
187        let response = match res {
188            Err(e) => {
189                return Err(Error::from(e));
190            }
191            Ok(resp) => resp,
192        };
193
194        let status_code = response.status().as_u16();
195        if status_code != 200 && status_code != 204 {
196            return Err(Error::from(status_code));
197        }
198
199        let data = match response.json::<RenewResponse>() {
200            Err(e) => Err(Error::from(e)),
201            Ok(json) => Ok(json),
202        };
203
204        let data = data.unwrap();
205
206        let current_time = SystemTime::now()
207            .duration_since(SystemTime::UNIX_EPOCH)
208            .unwrap()
209            .as_secs();
210        let duration = data.auth.lease_duration;
211        let renewable = data.auth.renewable;
212
213        self.token.set_renewable(renewable);
214
215        // The times should again be set at the end after everything else is done already
216        self.token.set_start(current_time);
217        self.token.set_duration(duration);
218
219        Ok(())
220    }
221}
222
223impl Session {
224    /// This function returns a new Approle-Auth-Session that can be used
225    /// as an authenticator for the vault client itself
226    pub fn new(role_id: String, secret_id: String) -> Result<Session, Error> {
227        let approle = ApproleLogin { role_id, secret_id };
228
229        Ok(Session {
230            approle,
231            token: internals::TokenContainer::new(),
232        })
233    }
234}