1use async_trait::async_trait;
2use kanidm_hsm_crypto::provider::BoxedDynTpm;
3use kanidm_unix_common::unix_proto::{
4 DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse,
5};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::BTreeMap;
9use std::fmt;
10use std::time::SystemTime;
11use tokio::sync::broadcast;
12use uuid::Uuid;
1314pub type XKeyId = String;
1516pub use kanidm_hsm_crypto as tpm;
1718/// Errors that the IdProvider may return. These drive the resolver state machine
19/// and should be carefully selected to match your expected errors.
20#[derive(Debug)]
21pub enum IdpError {
22/// An error occurred in the underlying communication to the Idp. A timeout or
23 /// or other communication issue exists. The resolver will take this provider
24 /// offline.
25Transport,
26/// The provider is online but the provider module is not current authorised with
27 /// the idp. After returning this error the operation will be retried after a
28 /// successful authentication.
29ProviderUnauthorised,
30/// The provider made an invalid or illogical request to the idp, and a result
31 /// is not able to be provided to the resolver.
32BadRequest,
33/// The idp has indicated that the requested resource does not exist and should
34 /// be considered deleted, removed, or not present.
35NotFound,
36/// The idp was unable to perform an operation on the underlying hsm keystorage
37KeyStore,
38/// The idp failed to interact with the configured TPM
39Tpm,
40}
4142pub enum UserTokenState {
43/// Indicate to the resolver that the cached UserToken should be used, if present.
44UseCached,
45/// The requested entity is not found, or has been removed.
46NotFound,
4748/// Update the cache state with the data found in this UserToken.
49Update(UserToken),
50}
5152pub enum GroupTokenState {
53/// Indicate to the resolver that the cached GroupToken should be used, if present.
54UseCached,
55/// The requested entity is not found, or has been removed.
56NotFound,
5758/// Update the cache state with the data found in this GroupToken.
59Update(GroupToken),
60}
6162#[derive(Debug, Clone, PartialEq, Eq, Hash)]
63pub enum Id {
64 Name(String),
65 Gid(u32),
66}
6768#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq, Hash)]
69pub enum ProviderOrigin {
70// To allow transition, we have an ignored type that effectively
71 // causes these items to be nixed.
72#[default]
73Ignore,
74/// Provided by /etc/passwd or /etc/group
75System,
76 Kanidm,
77}
7879impl fmt::Display for ProviderOrigin {
80fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81match self {
82 ProviderOrigin::Ignore => {
83write!(f, "Ignored")
84 }
85 ProviderOrigin::System => {
86write!(f, "System")
87 }
88 ProviderOrigin::Kanidm => {
89write!(f, "Kanidm")
90 }
91 }
92 }
93}
9495#[derive(Debug, Serialize, Deserialize, Clone)]
96pub struct GroupToken {
97#[serde(default)]
98pub provider: ProviderOrigin,
99pub name: String,
100pub spn: String,
101pub uuid: Uuid,
102pub gidnumber: u32,
103104#[serde(flatten)]
105pub extra_keys: BTreeMap<XKeyId, Value>,
106}
107108#[derive(Debug, Serialize, Deserialize, Clone)]
109pub struct UserToken {
110#[serde(default)]
111pub provider: ProviderOrigin,
112113pub name: String,
114pub spn: String,
115pub uuid: Uuid,
116pub gidnumber: u32,
117pub displayname: String,
118pub shell: Option<String>,
119pub groups: Vec<GroupToken>,
120121// Could there be a better type here?
122pub sshkeys: Vec<String>,
123// Defaults to false.
124pub valid: bool,
125126// These are opaque extra keys that the provider can interpret for internal
127 // functions.
128#[serde(flatten)]
129pub extra_keys: BTreeMap<XKeyId, Value>,
130}
131132#[derive(Debug)]
133pub enum AuthCredHandler {
134 Password,
135 DeviceAuthorizationGrant,
136/// Additional data required by the provider to complete the
137 /// authentication, but not required by PAM
138 ///
139 /// Sadly due to how this is passed around we can't make this a
140 /// generic associated type, else it would have to leak up to the
141 /// daemon.
142 ///
143 /// ⚠️ TODO: Optimally this should actually be a tokio oneshot receiver
144 /// with the decision from a task that is spawned.
145MFA {
146 data: Vec<String>,
147 },
148 SetupPin,
149 Pin,
150}
151152pub enum AuthRequest {
153 Password,
154 DeviceAuthorizationGrant {
155 data: DeviceAuthorizationResponse,
156 },
157 MFACode {
158 msg: String,
159 },
160 MFAPoll {
161/// Message to display to the user.
162msg: String,
163/// Interval in seconds between poll attempts.
164polling_interval: u32,
165 },
166 MFAPollWait,
167 SetupPin {
168/// Message to display to the user.
169msg: String,
170 },
171 Pin,
172}
173174#[allow(clippy::from_over_into)]
175impl Into<PamAuthResponse> for AuthRequest {
176fn into(self) -> PamAuthResponse {
177match self {
178 AuthRequest::Password => PamAuthResponse::Password,
179 AuthRequest::DeviceAuthorizationGrant { data } => {
180 PamAuthResponse::DeviceAuthorizationGrant { data }
181 }
182 AuthRequest::MFACode { msg } => PamAuthResponse::MFACode { msg },
183 AuthRequest::MFAPoll {
184 msg,
185 polling_interval,
186 } => PamAuthResponse::MFAPoll {
187 msg,
188 polling_interval,
189 },
190 AuthRequest::MFAPollWait => PamAuthResponse::MFAPollWait,
191 AuthRequest::SetupPin { msg } => PamAuthResponse::SetupPin { msg },
192 AuthRequest::Pin => PamAuthResponse::Pin,
193 }
194 }
195}
196197pub enum AuthResult {
198 Success,
199 SuccessUpdate { new_token: UserToken },
200 Denied,
201 Next(AuthRequest),
202}
203204#[async_trait]
205#[allow(clippy::too_many_arguments)]
206pub trait IdProvider {
207/// Retrieve this providers origin
208fn origin(&self) -> ProviderOrigin;
209210/// Attempt to go online *immediately*
211async fn attempt_online(&self, _tpm: &mut BoxedDynTpm, _now: SystemTime) -> bool;
212213/// Mark that this provider should attempt to go online next time it
214 /// receives a request
215async fn mark_next_check(&self, _now: SystemTime);
216217/// Force this provider offline immediately.
218async fn mark_offline(&self);
219220/// Determine if this provider has a configured extension of a local system group
221 /// with remote members.
222fn has_map_group(&self, local: &str) -> Option<&Id>;
223224// This is similar to a "domain join" process. What do we actually need to pass here
225 // for this to work for kanidm or himmelblau? Should we make it take a generic?
226 /*
227 async fn configure_machine_identity(
228 &self,
229 _keystore: &mut KeyStoreTxn,
230 _tpm: &mut BoxedDynTpm,
231 _machine_key: &tpm::MachineKey,
232 ) -> Result<(), IdpError> {
233 Ok(())
234 }
235 */
236237async fn unix_user_get(
238&self,
239 _id: &Id,
240 _token: Option<&UserToken>,
241 _tpm: &mut BoxedDynTpm,
242 _now: SystemTime,
243 ) -> Result<UserTokenState, IdpError>;
244245async fn unix_user_online_auth_init(
246&self,
247 _account_id: &str,
248 _token: &UserToken,
249 _tpm: &mut BoxedDynTpm,
250 _shutdown_rx: &broadcast::Receiver<()>,
251 ) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
252253async fn unix_user_online_auth_step(
254&self,
255 _account_id: &str,
256 _current_token: Option<&UserToken>,
257 _cred_handler: &mut AuthCredHandler,
258 _pam_next_req: PamAuthRequest,
259 _tpm: &mut BoxedDynTpm,
260 _shutdown_rx: &broadcast::Receiver<()>,
261 ) -> Result<AuthResult, IdpError>;
262263async fn unix_unknown_user_online_auth_init(
264&self,
265 _account_id: &str,
266 _tpm: &mut BoxedDynTpm,
267 _shutdown_rx: &broadcast::Receiver<()>,
268 ) -> Result<Option<(AuthRequest, AuthCredHandler)>, IdpError>;
269270async fn unix_user_offline_auth_init(
271&self,
272 _token: &UserToken,
273 ) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
274275// I thought about this part of the interface a lot. we could have the
276 // provider actually need to check the password or credentials, but then
277 // we need to rework the tpm/crypto engine to be an argument to pass here
278 // as well the cached credentials.
279 //
280 // As well, since this is "offline auth" the provider isn't really "doing"
281 // anything special here - when you say you want offline password auth, the
282 // resolver can just do it for you for all the possible implementations.
283 // This is similar for offline ctap2 as well, or even offline totp.
284 //
285 // I think in the future we could reconsider this and let the provider be
286 // involved if there is some "custom logic" or similar that is needed but
287 // for now I think making it generic is a good first step and we can change
288 // it later.
289 //
290 // EDIT 04042024: When we're performing an offline PIN auth, the PIN can
291 // unlock the associated TPM key. While we can't perform a full request
292 // for an auth token, we can verify that the PIN successfully unlocks the
293 // TPM key.
294async fn unix_user_offline_auth_step(
295&self,
296 _current_token: Option<&UserToken>,
297 _session_token: &UserToken,
298 _cred_handler: &mut AuthCredHandler,
299 _pam_next_req: PamAuthRequest,
300 _tpm: &mut BoxedDynTpm,
301 ) -> Result<AuthResult, IdpError>;
302303async fn unix_user_authorise(&self, _token: &UserToken) -> Result<Option<bool>, IdpError>;
304305async fn unix_group_get(
306&self,
307 id: &Id,
308 _tpm: &mut BoxedDynTpm,
309 _now: SystemTime,
310 ) -> Result<GroupTokenState, IdpError>;
311}