Skip to main content

hc_vault/kubernetes/
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 Kubernetes Login
10#[derive(Clone, Serialize)]
11pub struct KubernetesLogin {
12    /// The JWT Token to use for authentication
13    pub jwt: String,
14    /// The Role that you want to login as
15    pub role: String,
16}
17
18#[derive(Deserialize)]
19#[allow(dead_code)]
20struct KubernetesMetadata {
21    role: String,
22    service_account_name: String,
23    service_account_namespace: String,
24    service_account_secret_name: String,
25    service_account_uid: String,
26}
27
28#[derive(Deserialize)]
29#[allow(dead_code)]
30struct KubernetesAuth {
31    client_token: String,
32    accessor: String,
33    policies: Vec<String>,
34    metadata: KubernetesMetadata,
35    lease_duration: u64,
36    renewable: bool,
37}
38
39#[derive(Deserialize)]
40struct KubernetesResponse {
41    auth: KubernetesAuth,
42}
43
44#[derive(Deserialize)]
45#[allow(dead_code)]
46struct GeneralAuth {
47    client_token: String,
48    policies: Vec<String>,
49    lease_duration: u64,
50    renewable: bool,
51}
52
53#[derive(Deserialize)]
54struct RenewResponse {
55    auth: GeneralAuth,
56}
57
58/// The Auth session for the Kubernetes Backend, used by the vault client
59/// to authenticate requests
60pub struct Session {
61    kubernetes: KubernetesLogin,
62    token: internals::TokenContainer,
63}
64
65impl AuthTrait for Session {
66    fn is_expired(&self) -> bool {
67        let start_time = self.token.get_start();
68        let current_time = SystemTime::now()
69            .duration_since(SystemTime::UNIX_EPOCH)
70            .unwrap()
71            .as_secs();
72
73        let elapsed = current_time - start_time;
74        let duration = self.token.get_duration();
75
76        elapsed >= duration
77    }
78
79    fn get_token(&self) -> String {
80        // Safety:
81        // This is indirectly synchronized as this function is only called
82        // while the session is not being updated and therefore the token
83        // will not be changing while this function is being called
84        match self.token.get_token() {
85            None => String::from(""),
86            Some(s) => s,
87        }
88    }
89
90    fn auth(&self, vault_url: &str) -> Result<(), Error> {
91        let mut login_url = match Url::parse(vault_url) {
92            Err(e) => return Err(Error::from(e)),
93            Ok(url) => url,
94        };
95        login_url = match login_url.join("v1/auth/kubernetes/login") {
96            Err(e) => return Err(Error::from(e)),
97            Ok(u) => u,
98        };
99
100        let http_client = reqwest::blocking::Client::new();
101        let response = match http_client.post(login_url).json(&self.kubernetes).send() {
102            Err(e) => return Err(Error::from(e)),
103            Ok(resp) => resp,
104        };
105
106        let status_code = response.status().as_u16();
107        if status_code != 200 && status_code != 204 {
108            return Err(Error::from(status_code));
109        }
110
111        let data = match response.json::<KubernetesResponse>() {
112            Err(e) => Err(Error::from(e)),
113            Ok(json) => Ok(json),
114        };
115
116        let data = data.unwrap();
117
118        let token = data.auth.client_token;
119        let current_time = SystemTime::now()
120            .duration_since(SystemTime::UNIX_EPOCH)
121            .unwrap()
122            .as_secs();
123        let duration = data.auth.lease_duration;
124
125        // Safety:
126        // This is safe to do, because we are the only thread accessing the
127        // Token at that moment so we can perform the update without any other
128        // means of synchronization
129        self.token.set_token(token);
130
131        self.token.set_renewable(data.auth.renewable);
132
133        // Update the Times afterwards, as they are basically acting like a switch
134        // that once they are "valid" again, every thread can read the token
135        // agai. So once they are set we have to assume that threads will
136        // immediately try to access the token
137        self.token.set_start(current_time);
138        self.token.set_duration(duration);
139
140        Ok(())
141    }
142
143    fn is_renewable(&self) -> bool {
144        self.token.get_renewable()
145    }
146
147    fn get_total_duration(&self) -> u64 {
148        self.token.get_duration()
149    }
150
151    fn renew(&self, vault_url: &str) -> Result<(), Error> {
152        let mut renew_url = match Url::parse(vault_url) {
153            Err(e) => {
154                return Err(Error::from(e));
155            }
156            Ok(url) => url,
157        };
158        renew_url = match renew_url.join("v1/auth/token/renew-self") {
159            Err(e) => {
160                return Err(Error::from(e));
161            }
162            Ok(u) => u,
163        };
164
165        let http_client = reqwest::blocking::Client::new();
166        let res = http_client
167            .post(renew_url)
168            .header("X-Vault-Token", self.token.get_token().unwrap())
169            .send();
170
171        let response = match res {
172            Err(e) => {
173                return Err(Error::from(e));
174            }
175            Ok(resp) => resp,
176        };
177
178        let status_code = response.status().as_u16();
179        if status_code != 200 && status_code != 204 {
180            return Err(Error::from(status_code));
181        }
182
183        let data = match response.json::<RenewResponse>() {
184            Err(e) => Err(Error::from(e)),
185            Ok(json) => Ok(json),
186        };
187
188        let data = data.unwrap();
189
190        let current_time = SystemTime::now()
191            .duration_since(SystemTime::UNIX_EPOCH)
192            .unwrap()
193            .as_secs();
194        let duration = data.auth.lease_duration;
195        let renewable = data.auth.renewable;
196
197        self.token.set_renewable(renewable);
198
199        // The times should again be set at the end after everything else is done already
200        self.token.set_start(current_time);
201        self.token.set_duration(duration);
202
203        Ok(())
204    }
205}
206
207impl Session {
208    /// This is used to obtain a new Auth-Session for the Kubernetes
209    /// Auth-Backend
210    pub fn new(role: String, jwt: String) -> Result<Session, Error> {
211        Ok(Session {
212            kubernetes: KubernetesLogin { role, jwt },
213            token: internals::TokenContainer::new(),
214        })
215    }
216}