Skip to main content

openstack_keystone_core/k8s_auth/
service.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13// SPDX-License-Identifier: Apache-2.0
14//! # Kubernetes authentication.
15
16use std::collections::HashMap;
17use std::sync::Arc;
18
19use async_trait::async_trait;
20use reqwest::Client;
21use tokio::sync::RwLock;
22
23use crate::k8s_auth::{K8sAuthProviderError, backend::K8sAuthBackend, types::*};
24use crate::keystone::ServiceState;
25use crate::plugin_manager::PluginManagerApi;
26use crate::token::types::TokenRestriction;
27use crate::{auth::AuthenticatedInfo, config::Config};
28
29/// K8s Auth provider.
30pub struct K8sAuthService {
31    /// Backend driver.
32    pub(super) backend_driver: Arc<dyn K8sAuthBackend>,
33
34    /// Reqwest client.
35    pub(super) http_clients: RwLock<HashMap<String, Arc<Client>>>,
36}
37
38impl K8sAuthService {
39    pub fn new<P: PluginManagerApi>(
40        config: &Config,
41        plugin_manager: &P,
42    ) -> Result<Self, K8sAuthProviderError> {
43        let backend_driver = plugin_manager
44            .get_k8s_auth_backend(config.k8s_auth.driver.clone())?
45            .clone();
46        Ok(Self {
47            backend_driver,
48            http_clients: RwLock::new(HashMap::new()),
49        })
50    }
51}
52
53#[async_trait]
54impl K8sAuthApi for K8sAuthService {
55    /// Authenticate (exchange) the K8s Service account token.
56    async fn authenticate_by_k8s_sa_token(
57        &self,
58        state: &ServiceState,
59        req: &K8sAuthRequest,
60    ) -> Result<(AuthenticatedInfo, TokenRestriction), K8sAuthProviderError> {
61        self.authenticate(state, req).await
62    }
63
64    /// Register new K8s auth instance.
65    #[tracing::instrument(skip(self, state))]
66    async fn create_auth_instance(
67        &self,
68        state: &ServiceState,
69        instance: K8sAuthInstanceCreate,
70    ) -> Result<K8sAuthInstance, K8sAuthProviderError> {
71        let mut new = instance;
72        if new.id.is_none() {
73            new.id = Some(uuid::Uuid::new_v4().simple().to_string());
74        }
75        self.backend_driver.create_auth_instance(state, new).await
76    }
77
78    /// Register new K8s auth role.
79    #[tracing::instrument(skip(self, state))]
80    async fn create_auth_role(
81        &self,
82        state: &ServiceState,
83        role: K8sAuthRoleCreate,
84    ) -> Result<K8sAuthRole, K8sAuthProviderError> {
85        let mut new = role;
86        if new.id.is_none() {
87            new.id = Some(uuid::Uuid::new_v4().simple().to_string());
88        }
89        self.backend_driver.create_auth_role(state, new).await
90    }
91
92    /// Delete K8s auth provider.
93    #[tracing::instrument(skip(self, state))]
94    async fn delete_auth_instance<'a>(
95        &self,
96        state: &ServiceState,
97        id: &'a str,
98    ) -> Result<(), K8sAuthProviderError> {
99        self.backend_driver.delete_auth_instance(state, id).await
100    }
101
102    /// Delete K8s auth role.
103    #[tracing::instrument(skip(self, state))]
104    async fn delete_auth_role<'a>(
105        &self,
106        state: &ServiceState,
107        id: &'a str,
108    ) -> Result<(), K8sAuthProviderError> {
109        self.backend_driver.delete_auth_role(state, id).await
110    }
111
112    /// Register new K8s auth instance.
113    #[tracing::instrument(skip(self, state))]
114    async fn get_auth_instance<'a>(
115        &self,
116        state: &ServiceState,
117        id: &'a str,
118    ) -> Result<Option<K8sAuthInstance>, K8sAuthProviderError> {
119        self.backend_driver.get_auth_instance(state, id).await
120    }
121
122    /// Register new K8s auth role.
123    #[tracing::instrument(skip(self, state))]
124    async fn get_auth_role<'a>(
125        &self,
126        state: &ServiceState,
127        id: &'a str,
128    ) -> Result<Option<K8sAuthRole>, K8sAuthProviderError> {
129        self.backend_driver.get_auth_role(state, id).await
130    }
131
132    /// List K8s auth instances.
133    #[tracing::instrument(skip(self, state))]
134    async fn list_auth_instances(
135        &self,
136        state: &ServiceState,
137        params: &K8sAuthInstanceListParameters,
138    ) -> Result<Vec<K8sAuthInstance>, K8sAuthProviderError> {
139        self.backend_driver.list_auth_instances(state, params).await
140    }
141
142    /// List K8s auth roles.
143    #[tracing::instrument(skip(self, state))]
144    async fn list_auth_roles(
145        &self,
146        state: &ServiceState,
147        params: &K8sAuthRoleListParameters,
148    ) -> Result<Vec<K8sAuthRole>, K8sAuthProviderError> {
149        self.backend_driver.list_auth_roles(state, params).await
150    }
151
152    /// Update K8s auth instance.
153    #[tracing::instrument(skip(self, state))]
154    async fn update_auth_instance<'a>(
155        &self,
156        state: &ServiceState,
157        id: &'a str,
158        data: K8sAuthInstanceUpdate,
159    ) -> Result<K8sAuthInstance, K8sAuthProviderError> {
160        self.backend_driver
161            .update_auth_instance(state, id, data)
162            .await
163    }
164
165    /// Update K8s auth role.
166    #[tracing::instrument(skip(self, state))]
167    async fn update_auth_role<'a>(
168        &self,
169        state: &ServiceState,
170        id: &'a str,
171        data: K8sAuthRoleUpdate,
172    ) -> Result<K8sAuthRole, K8sAuthProviderError> {
173        self.backend_driver.update_auth_role(state, id, data).await
174    }
175}
176
177#[cfg(test)]
178pub(crate) mod tests {
179    use std::sync::Arc;
180
181    use super::*;
182    use crate::k8s_auth::backend::MockK8sAuthBackend;
183    use crate::tests::get_mocked_state;
184
185    #[tokio::test]
186    async fn test_create_auth_instance() {
187        let state = get_mocked_state(None, None);
188        let mut backend = MockK8sAuthBackend::default();
189        backend
190            .expect_create_auth_instance()
191            .returning(|_, _| Ok(K8sAuthInstance::default()));
192        let provider = K8sAuthService {
193            backend_driver: Arc::new(backend),
194            http_clients: RwLock::new(HashMap::new()),
195        };
196
197        assert!(
198            provider
199                .create_auth_instance(
200                    &state,
201                    K8sAuthInstanceCreate {
202                        ca_cert: Some("ca".into()),
203                        disable_local_ca_jwt: Some(true),
204                        domain_id: "did".into(),
205                        enabled: true,
206                        host: "host".into(),
207                        id: Some("id".into()),
208                        name: Some("name".into()),
209                    }
210                )
211                .await
212                .is_ok()
213        );
214    }
215
216    #[tokio::test]
217    async fn test_create_auth_role() {
218        let state = get_mocked_state(None, None);
219        let mut backend = MockK8sAuthBackend::default();
220        backend
221            .expect_create_auth_role()
222            .returning(|_, _| Ok(K8sAuthRole::default()));
223        let provider = K8sAuthService {
224            backend_driver: Arc::new(backend),
225            http_clients: RwLock::new(HashMap::new()),
226        };
227
228        assert!(
229            provider
230                .create_auth_role(
231                    &state,
232                    K8sAuthRoleCreate {
233                        auth_instance_id: "cid".into(),
234                        bound_audience: Some("aud".into()),
235                        bound_service_account_names: vec!["a".into(), "b".into()],
236                        bound_service_account_namespaces: vec!["na".into(), "nb".into()],
237                        domain_id: "did".into(),
238                        enabled: true,
239                        id: Some("id".into()),
240                        name: "name".into(),
241                        token_restriction_id: "trid".into(),
242                    }
243                )
244                .await
245                .is_ok()
246        );
247    }
248}