krill 0.16.0

Resource Public Key Infrastructure (RPKI) daemon
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
//! Authorization for the API

use std::sync::Arc;
use log::{info, log_enabled, trace};
use rpki::ca::idexchange::MyHandle;
use serde::Serialize;
use tokio::runtime;
use crate::api::admin::Token;
use crate::commons::KrillResult;
use crate::commons::actor::Actor;
use crate::commons::error::ApiAuthError;
use crate::config::{AuthType, Config};
#[cfg(unix)]
use crate::daemon::http::auth::providers::unix_user;
use crate::daemon::http::request::HyperRequest;
use crate::daemon::http::response::HttpResponse;
use super::{Permission, Role};
use super::providers::admin_token;
#[cfg(feature = "multi-user")]
use super::providers::{config_file, openid_connect};


//------------ AuthProvider --------------------------------------------------

/// An AuthProvider authenticates and authorizes a given token.
///
/// An AuthProvider is expected to configure itself using the global Krill
/// from configuration. This avoids propagation of potentially many provider
/// specific configuration values from the calling code to the provider
/// implementation.
///
/// Each AuthProvider is responsible for answering questions related to:
///
///  * authentication - who are you and is it really you?
///  * authorization  - do you have the right to do the thing you want to do?
///  * discovery      - as an interactive client where should I send my users
///    to login and logout?
///  * introspection  - who is the currently "logged in" user?
///
/// This type is a wrapper around the available backend specific auth
/// providers that can be found in the [super::providers] module.
enum AuthProvider {
    Token(admin_token::AuthProvider),

    #[cfg(feature = "multi-user")]
    ConfigFile(config_file::AuthProvider),

    #[cfg(feature = "multi-user")]
    OpenIdConnect(openid_connect::AuthProvider),
}

impl From<admin_token::AuthProvider> for AuthProvider {
    fn from(provider: admin_token::AuthProvider) -> Self {
        AuthProvider::Token(provider)
    }
}

#[cfg(feature = "multi-user")]
impl From<config_file::AuthProvider> for AuthProvider {
    fn from(provider: config_file::AuthProvider) -> Self {
        AuthProvider::ConfigFile(provider)
    }
}

#[cfg(feature = "multi-user")]
impl From<openid_connect::AuthProvider> for AuthProvider {
    fn from(provider: openid_connect::AuthProvider) -> Self {
        AuthProvider::OpenIdConnect(provider)
    }
}

impl AuthProvider {
    /// Authenticates a user from information included in an HTTP request.
    ///
    /// Returns `Ok(None)` to indicate that no authentication information
    /// was present in the request and the request should thus be treated
    /// as not anonymous.
    ///
    /// If authentication succeeded, returns the auth info. If it failed,
    /// it either returns an auth info created via [`AuthInfo::error`] or
    /// just a plain error which the caller needs to convert.
    pub async fn authenticate(
        &self,
        request: &HyperRequest,
    ) -> Result<Option<(AuthInfo, Option<Token>)>, ApiAuthError> {
        match &self {
            AuthProvider::Token(provider) => provider.authenticate(request),
            #[cfg(feature = "multi-user")]
            AuthProvider::ConfigFile(provider) => {
                provider.authenticate(request).await
            }
            #[cfg(feature = "multi-user")]
            AuthProvider::OpenIdConnect(provider) => {
                provider.authenticate(request).await
            }
        }
    }

    /// Returns an HTTP text response with the login URL.
    pub async fn get_login_url(&self) -> KrillResult<HttpResponse> {
        match &self {
            AuthProvider::Token(provider) => provider.get_login_url(),
            #[cfg(feature = "multi-user")]
            AuthProvider::ConfigFile(provider) => provider.get_login_url(),
            #[cfg(feature = "multi-user")]
            AuthProvider::OpenIdConnect(provider) => {
                provider.get_login_url().await
            }
        }
    }

    /// Establishes a client session from credentials in an HTTP request.
    pub async fn login(
        &self,
        request: &HyperRequest,
    ) -> KrillResult<LoggedInUser> {
        match &self {
            AuthProvider::Token(provider) => provider.login(request),
            #[cfg(feature = "multi-user")]
            AuthProvider::ConfigFile(provider) => {
                provider.login(request).await
            }
            #[cfg(feature = "multi-user")]
            AuthProvider::OpenIdConnect(provider) => {
                provider.login(request).await
            }
        }
    }

    /// Returns an HTTP text response with the logout URL.
    pub async fn logout(
        &self,
        request: &HyperRequest,
    ) -> KrillResult<HttpResponse> {
        match &self {
            AuthProvider::Token(provider) => provider.logout(request),
            #[cfg(feature = "multi-user")]
            AuthProvider::ConfigFile(provider) => {
                provider.logout(request).await
            }
            #[cfg(feature = "multi-user")]
            AuthProvider::OpenIdConnect(provider) => {
                provider.logout(request).await
            }
        }
    }

    /// Returns the size of the login session cache.
    pub async fn login_session_cache_size(&self) -> usize {
        match self {
            AuthProvider::Token(_) => 0,
            #[cfg(feature = "multi-user")]
            AuthProvider::ConfigFile(provider) => {
                provider.cache_size().await
            }
            #[cfg(feature = "multi-user")]
            AuthProvider::OpenIdConnect(provider) => {
                provider.cache_size().await
            }
        }
    }

    /// If necessary, spawns a Tokio task sweeping the session cache.
    #[allow(unused_variables)]
    pub fn spawn_sweep(&self, runtime: &runtime::Handle) {
        match self {
            AuthProvider::Token(_) => { }
            #[cfg(feature = "multi-user")]
            AuthProvider::ConfigFile(provider) => {
                provider.spawn_sweep(runtime)
            }
            #[cfg(feature = "multi-user")]
            AuthProvider::OpenIdConnect(provider) => {
                provider.spawn_sweep(runtime)
            }
        }
    }
}


//------------ Authorizer ----------------------------------------------------

/// Checks authorizations when the API is accessed.
pub struct Authorizer {
    /// The auth provider configured by the user.
    primary_provider: AuthProvider,

    /// A fallback token auth provider when it isn’t the primary provider.
    ///
    /// This is necessary to support the command line client which only
    /// supports admin token authentication.
    legacy_provider: Option<admin_token::AuthProvider>,

    /// A UNIX socket auth provider, for when the command line client is used
    /// from the local machine
    #[cfg(unix)]
    unix_socket_provider: unix_user::AuthProvider,
}

impl Authorizer {
    /// Creates an instance of the Authorizer.
    ///
    /// The authorizer will be created according to information provided via
    /// `config`.
    pub fn new(
        config: Arc<Config>,
    ) -> KrillResult<Self> {
        let (primary_provider, legacy_provider) = match config.auth_type {
            AuthType::AdminToken => {
                (admin_token::AuthProvider::new(config.clone()).into(), None)
            }
            #[cfg(feature = "multi-user")]
            AuthType::ConfigFile => {
                (
                    config_file::AuthProvider::new(&config)?.into(),
                    Some(admin_token::AuthProvider::new(config.clone()))
                )
            }
            #[cfg(feature = "multi-user")]
            AuthType::OpenIDConnect => {
                (
                    openid_connect::AuthProvider::new(config.clone())?.into(),
                    Some(admin_token::AuthProvider::new(config.clone()))
                )
            }
        };

        Ok(Authorizer {
            primary_provider,
            legacy_provider,
            #[cfg(unix)]
            unix_socket_provider: unix_user::AuthProvider::new(config.clone())?
        })
    }

    /// Authenticates an HTTP request.
    ///
    /// The method will always return authentication information. It will also
    /// return an optional token that should be added to a response as a
    /// Bearer token.
    ///
    /// If there was no authentiation information in the request, the returned
    /// auth info will indicate an anonymous user which will fail all
    /// permission checks with “insufficient permissions.”
    ///
    /// If authentication failed, the returned auth info will also indicate
    /// an anonymous user but it will fail permission checks with appropriate 
    /// error information.
    pub async fn authenticate_request(
        &self, request: &HyperRequest
    ) -> (AuthInfo, Option<Token>) {
        trace!("Determining actor for request {:?}", &request);

        // Try the legacy provider first, if any.
        let authenticate_res = match &self.legacy_provider {
            Some(provider) => provider.authenticate(request),
            None => Ok(None),
        };

        // Try the real provider if we did not already successfully
        // authenticate. This ignores any possible errors thrown by the
        // legacy provider.
        let authenticate_res = match authenticate_res {
            Ok(Some(res)) => Ok(Some(res)),
            _ => self.primary_provider.authenticate(request).await,
        };

        // Try whether this authentication came from a UNIX socket
        #[cfg(unix)]
        let authenticate_res = match authenticate_res {
            Ok(Some(res)) => Ok(Some(res)),
            _ => self.unix_socket_provider.authenticate(request),
        };

        // Create an actor based on the authentication result
        let res = match authenticate_res {
            // authentication success
            Ok(Some(res)) => res,

            // authentication failure
            Ok(None) => (AuthInfo::anonymous(), None),

            // error during authentication
            Err(err) => (AuthInfo::error(err), None),
        };

        trace!("AuthInfo determination result: {res:?}");

        res
    }

    /// Returns an HTTP text response with the login URL.
    pub async fn get_login_url(&self) -> KrillResult<HttpResponse> {
        self.primary_provider.get_login_url().await
    }

    /// Establishes a client session from credentials in an HTTP request.
    pub async fn login(
        &self, request: &HyperRequest
    ) -> KrillResult<LoggedInUser> {
        let user = self.primary_provider.login(request).await?;

        if log_enabled!(log::Level::Trace) {
            trace!("User logged in: {:?}", &user);
        } else {
            info!("User logged in: {}, role: {}", user.id(), user.role());
        }

        Ok(user)
    }

    /// Returns an HTTP text response with the logout URL.
    pub async fn logout(
        &self,
        request: &HyperRequest,
    ) -> KrillResult<HttpResponse> {
        self.primary_provider.logout(request).await
    }

    /// Returns the size of the login session cache.
    pub async fn login_session_cache_size(&self) -> usize {
        self.primary_provider.login_session_cache_size().await
    }

    /// If necessary, spawns a Tokio task sweeping the session cache.
    pub fn spawn_sweep(&self, runtime: &runtime::Handle) {
        self.primary_provider.spawn_sweep(runtime)
    }
}


//------------ LoggedInUser --------------------------------------------------

/// Information to be returned to the caller after login.
///
/// This may be serialized into a JSON response.
#[derive(Serialize, Debug)]
pub struct LoggedInUser {
    /// The API token to use in subsequent calls.
    token: Token,

    /// The user ID.
    id: Arc<str>,

    /// The user attributes.
    ///
    /// This used to be a hash map with values decided upon by the auth
    /// provider but we now only and always have a role attribute. However,
    /// in order to serialize into the JSON expected by the UI, this still
    /// needs to be a struct.
    attributes: LoggedInUserAttributes,
}

#[derive(Serialize, Debug)]
pub struct LoggedInUserAttributes {
    role: Arc<str>,
}

impl LoggedInUser {
    pub fn new(token: Token, id: Arc<str>, role: Arc<str>) -> Self {
        LoggedInUser {
            token,
            id,
            attributes: LoggedInUserAttributes { role }
        }
    }

    pub fn token(&self) -> &Token {
        &self.token
    }

    pub fn id(&self) -> &str {
        &self.id
    }

    pub fn role(&self) -> &str {
        self.attributes.role.as_ref()
    }

    pub fn attributes(&self) -> &impl Serialize {
        &self.attributes
    }
}


//------------ AuthInfo ------------------------------------------------------

/// Information about the result of trying to authenticate a request.
#[derive(Clone, Debug)]
pub struct AuthInfo {
    /// The actor for the authenticated user.
    actor: Actor,

    /// Access permissions.
    ///
    /// This is either a role which we consult to determine access
    /// permissions or an authentication error to return instead.
    permissions: Result<Arc<Role>, ApiAuthError>,
}

impl AuthInfo {
    /// Creates auth info for the given user ID and role.
    pub fn user(
        user_id: impl Into<Arc<str>>,
        role: Arc<Role>,
    ) -> Self {
        Self {
            actor: Actor::user(user_id),
            permissions: Ok(role),
        }
    }

    /// Creates auth info for the testbed actor.
    pub fn testbed() -> Self {
        Self::user("testbed", Role::testbed().into())
    }

    /// Creates auth info for the anonymous actor.
    ///
    /// This actor fails all permission checks with insufficient permissions.
    fn anonymous() -> Self {
        Self {
            actor: Actor::anonymous(),
            permissions: Ok(Role::anonymous().into()),
        }
    }

    /// Creates auth info for an authentication failure.
    fn error(err: ApiAuthError) -> Self {
        Self {
            actor: Actor::anonymous(),
            permissions: Err(err)
        }
    }

    /// Returns a reference to the actor.
    pub fn actor(&self) -> &Actor {
        &self.actor
    }

    /// Converts the auth info into the actor.
    pub fn into_actor(self) -> Actor {
        self.actor
    }

    /// Returns for permissions.
    pub fn has_permission(
        &self, 
        permission: Permission,
        resource: Option<&MyHandle>
    ) -> bool {
        self.check_permission(permission, resource).is_ok()
    }

    /// Checks permissions for an operation.
    ///
    /// Returns an authentication error if either the request was not
    /// authenticated or it was but the authenticated user does not have
    /// sufficient permissions.
    pub fn check_permission(
        &self,
        permission: Permission,
        resource: Option<&MyHandle>
    ) -> Result<(), ApiAuthError> {
        if self.permissions.as_ref().map_err(Clone::clone)?
            .is_allowed(permission, resource)
        {
            Ok(())
        }
        else {
            Err(ApiAuthError::insufficient_rights(
                &self.actor, permission, resource
            ))
        }
    }
}