container_registry/
auth.rs

1//! Authentication backends.
2//!
3//! The `container-registry` supports pluggable authentication, as anything that implements the
4//! [`AuthProvider`] trait can be used as an authentication (and authorization) backend. Included
5//! are implementations for the following types:
6//!
7//! * `Permissions`: The [`Permissions`] type itself is an auth provider, it will allow
8//!                  access with the given permissions to any non-anonymous client.
9//! * `HashMap<String, Secret<String>>`: A mapping of usernames to (unencrypted) passwords.
10//! * `Secret<String>`: Master password, ignores all usernames and just compares the password.
11//! * `Anonymous`: A decorator that wraps around another [`AuthProvider`], will grant a fixed set
12//!                of permissions to anonymous user, while deferring everything else to the inner
13//!                provider.
14//!
15//! All the above implementations deal with **authentication** only, once authorized, full
16//! write access to everything is granted.
17//!
18//! To provide some safety against accidentally leaking passwords via stray `Debug` implementations,
19//! this crate uses the [`sec`]'s crate [`Secret`] type.
20
21use std::{any::Any, collections::HashMap, str, sync::Arc};
22
23use axum::{
24    async_trait,
25    extract::FromRequestParts,
26    http::{
27        header::{self},
28        request::Parts,
29        StatusCode,
30    },
31};
32use sec::Secret;
33use thiserror::Error;
34
35use crate::{storage::ImageLocation, ImageDigest};
36
37use super::{
38    www_authenticate::{self},
39    ContainerRegistry,
40};
41
42/// A set of credentials supplied that has not been verified.
43#[derive(Debug)]
44pub enum Unverified {
45    /// A set of username and password credentials.
46    UsernameAndPassword {
47        /// The given username.
48        username: String,
49        /// The provided password.
50        password: Secret<String>,
51    },
52    /// No credentials were given.
53    NoCredentials,
54}
55
56impl Unverified {
57    /// Returns whether or not this set of unverified credentials is actually no credentials at all.
58    #[inline(always)]
59    pub fn is_no_credentials(&self) -> bool {
60        matches!(self, Unverified::NoCredentials)
61    }
62}
63
64#[async_trait]
65impl<S> FromRequestParts<S> for Unverified {
66    type Rejection = StatusCode;
67
68    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
69        if let Some(auth_header) = parts.headers.get(header::AUTHORIZATION) {
70            let (_unparsed, basic) = www_authenticate::basic_auth_response(auth_header.as_bytes())
71                .map_err(|_| StatusCode::BAD_REQUEST)?;
72
73            Ok(Unverified::UsernameAndPassword {
74                username: str::from_utf8(&basic.username)
75                    .map_err(|_| StatusCode::BAD_REQUEST)?
76                    .to_owned(),
77                password: Secret::new(
78                    str::from_utf8(&basic.password)
79                        .map_err(|_| StatusCode::BAD_REQUEST)?
80                        .to_owned(),
81                ),
82            })
83        } else {
84            Ok(Unverified::NoCredentials)
85        }
86    }
87}
88
89/// A set of credentials that has been validated.
90///
91/// Every [`AuthProvider`] is free to put [`Any`] type in the credentials and is guaranteed
92/// to be passed back only instances it created itself. Use [`Self::extract_ref`] to retrieve the
93/// passed in actual type.
94#[derive(Debug)]
95pub struct ValidCredentials(pub Box<dyn Any + Send + Sync>);
96
97impl ValidCredentials {
98    /// Creates a new set of valid credentials.
99    #[inline(always)]
100    pub fn new<T: Send + Sync + 'static>(inner: T) -> Self {
101        ValidCredentials(Box::new(inner))
102    }
103
104    /// Extracts a reference to the contained inner type.
105    pub fn extract_ref<T: 'static>(&self) -> &T {
106        self.0.downcast_ref::<T>().expect("could not downcast `ValidCredentials` into expected type - was auth provider called with the wrong set of credentials?")
107    }
108}
109
110#[async_trait]
111impl FromRequestParts<Arc<ContainerRegistry>> for ValidCredentials {
112    type Rejection = StatusCode;
113
114    #[inline(always)]
115    async fn from_request_parts(
116        parts: &mut Parts,
117        state: &Arc<ContainerRegistry>,
118    ) -> Result<Self, Self::Rejection> {
119        let unverified = Unverified::from_request_parts(parts, state).await?;
120
121        // We got a set of credentials, now verify.
122        match state.auth_provider.check_credentials(&unverified).await {
123            Some(creds) => Ok(creds),
124            None => Err(StatusCode::UNAUTHORIZED),
125        }
126    }
127}
128
129/// A set of permissions granted on a specific image location to a given set of credentials.
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131#[repr(u8)]
132pub enum Permissions {
133    /// Access forbidden.
134    NoAccess = 0,
135    /// Write only access.
136    WriteOnly = 2,
137    /// Read access.
138    ReadOnly = 4,
139    /// Read and write access.
140    ReadWrite = 6,
141}
142
143impl Permissions {
144    /// Returns whether or not permissions include read access.
145    #[inline(always)]
146    #[must_use = "should not check read permissions and discard the result"]
147    pub fn has_read_permission(self) -> bool {
148        match self {
149            Permissions::NoAccess | Permissions::WriteOnly => false,
150            Permissions::ReadOnly | Permissions::ReadWrite => true,
151        }
152    }
153
154    /// Returns whether or not permissions include write access.
155    #[inline(always)]
156    #[must_use = "should not check write permissions and discard the result"]
157    pub fn has_write_permission(self) -> bool {
158        match self {
159            Permissions::NoAccess | Permissions::ReadOnly => false,
160            Permissions::WriteOnly | Permissions::ReadWrite => true,
161        }
162    }
163
164    /// Returns an error if no read permission is included.
165    #[inline(always)]
166    pub fn require_read(self) -> Result<(), MissingPermission> {
167        if !self.has_read_permission() {
168            Err(MissingPermission)
169        } else {
170            Ok(())
171        }
172    }
173
174    /// Returns an error if no write permission is included.
175    #[inline(always)]
176    pub fn require_write(self) -> Result<(), MissingPermission> {
177        if !self.has_write_permission() {
178            Err(MissingPermission)
179        } else {
180            Ok(())
181        }
182    }
183}
184
185/// Error indicating a missing permission.
186#[derive(Debug, Error)]
187#[error("not permitted")]
188pub struct MissingPermission;
189
190/// An authentication and authorization provider.
191///
192/// At the moment, `container-registry` gives full access to any valid user.
193#[async_trait]
194pub trait AuthProvider: Send + Sync {
195    /// Checks whether the supplied unverified credentials are valid.
196    ///
197    /// Must return `None` if the credentials are not valid at all, malformed or similar.
198    ///
199    /// This is an **authenticating** function, returning `Some` indicates that the "login" was
200    /// successful, but makes not statement about what these credentials can actually access (see
201    /// `allowed_read()` and `allowed_write()` for authorization checks).
202    async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials>;
203
204    /// Determine permissions for given credentials at image location.
205    ///
206    /// This is an **authorizing** function that determines permissions for previously authenticated
207    /// credentials on a given [`ImageLocation`].
208    async fn image_permissions(
209        &self,
210        creds: &ValidCredentials,
211        image: &ImageLocation,
212    ) -> Permissions;
213
214    /// Determine permissions for given credentials to a specific blob.
215    ///
216    /// This is an **authorizing** function that determines permissions for previously authenticated
217    /// credentials on a given [`ImageLocation`].
218    ///
219    /// Note that blob permissions are only ever queried for reading blobs. Writing blobs does not
220    /// involve the uploader sending a hash beforehand, thus this function cannot be used to
221    /// implement a blacklist for specific blobs.
222    async fn blob_permissions(&self, creds: &ValidCredentials, blob: &ImageDigest) -> Permissions;
223}
224
225/// Anonymous access auth provider.
226///
227/// The [`Anonymous`] grants a fixed set of permissions to anonymous users, i.e. those not
228/// supplying any credentials at all. For others it defers to the wrapped [`AuthProvider`] `A`.
229#[derive(Debug)]
230pub struct Anonymous<A> {
231    anon_permissions: Permissions,
232    inner: A,
233}
234
235impl<A> Anonymous<A> {
236    /// Creates a new anonymous auth provider that decorates `inner`.
237    pub fn new(anon_permissions: Permissions, inner: A) -> Self {
238        Self {
239            anon_permissions,
240            inner,
241        }
242    }
243}
244
245/// A set of possibly anonymous credentials.
246#[derive(Debug)]
247enum AnonCreds {
248    /// No credentials provided, user is anonymous.
249    Anonymous,
250    /// Valid credentials supplied by inner auth provider.
251    Valid(ValidCredentials),
252}
253
254#[async_trait]
255impl<A> AuthProvider for Anonymous<A>
256where
257    A: AuthProvider,
258{
259    async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
260        match unverified {
261            Unverified::NoCredentials => Some(ValidCredentials::new(AnonCreds::Anonymous)),
262            _other => self.inner.check_credentials(unverified).await,
263        }
264    }
265
266    async fn image_permissions(
267        &self,
268        creds: &ValidCredentials,
269        image: &ImageLocation,
270    ) -> Permissions {
271        match creds.extract_ref::<AnonCreds>() {
272            AnonCreds::Anonymous => self.anon_permissions,
273            _other => self.inner.image_permissions(creds, image).await,
274        }
275    }
276
277    async fn blob_permissions(&self, creds: &ValidCredentials, blob: &ImageDigest) -> Permissions {
278        match creds.extract_ref::<AnonCreds>() {
279            AnonCreds::Anonymous => self.anon_permissions,
280            _other => self.inner.blob_permissions(creds, blob).await,
281        }
282    }
283}
284
285#[async_trait]
286impl AuthProvider for Permissions {
287    #[inline(always)]
288    async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
289        match unverified {
290            Unverified::NoCredentials => None,
291            _other => Some(ValidCredentials::new(())),
292        }
293    }
294
295    #[inline(always)]
296    async fn image_permissions(
297        &self,
298        _creds: &ValidCredentials,
299        _image: &ImageLocation,
300    ) -> Permissions {
301        *self
302    }
303
304    #[inline(always)]
305    async fn blob_permissions(
306        &self,
307        _creds: &ValidCredentials,
308        _blob: &ImageDigest,
309    ) -> Permissions {
310        *self
311    }
312}
313
314#[async_trait]
315impl AuthProvider for HashMap<String, Secret<String>> {
316    async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
317        match unverified {
318            Unverified::UsernameAndPassword {
319                username: unverified_username,
320                password: unverified_password,
321            } => {
322                if let Some(correct_password) = self.get(unverified_username) {
323                    if constant_time_eq::constant_time_eq(
324                        correct_password.reveal().as_bytes(),
325                        unverified_password.reveal().as_bytes(),
326                    ) {
327                        return Some(ValidCredentials::new(unverified_username.clone()));
328                    }
329                }
330
331                None
332            }
333            Unverified::NoCredentials => None,
334        }
335    }
336
337    #[inline(always)]
338    async fn image_permissions(
339        &self,
340        _creds: &ValidCredentials,
341        _image: &ImageLocation,
342    ) -> Permissions {
343        Permissions::ReadWrite
344    }
345
346    #[inline(always)]
347    async fn blob_permissions(
348        &self,
349        _creds: &ValidCredentials,
350        _blob: &ImageDigest,
351    ) -> Permissions {
352        Permissions::ReadWrite
353    }
354}
355
356#[async_trait]
357impl<T> AuthProvider for Box<T>
358where
359    T: AuthProvider,
360{
361    #[inline(always)]
362    async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
363        <T as AuthProvider>::check_credentials(self, unverified).await
364    }
365
366    #[inline(always)]
367    async fn image_permissions(
368        &self,
369        _creds: &ValidCredentials,
370        _image: &ImageLocation,
371    ) -> Permissions {
372        Permissions::ReadWrite
373    }
374
375    #[inline(always)]
376    async fn blob_permissions(
377        &self,
378        _creds: &ValidCredentials,
379        _blob: &ImageDigest,
380    ) -> Permissions {
381        Permissions::ReadWrite
382    }
383}
384
385#[async_trait]
386impl<T> AuthProvider for Arc<T>
387where
388    T: AuthProvider,
389{
390    #[inline(always)]
391    async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
392        <T as AuthProvider>::check_credentials(self, unverified).await
393    }
394
395    #[inline(always)]
396    async fn image_permissions(
397        &self,
398        _creds: &ValidCredentials,
399        _image: &ImageLocation,
400    ) -> Permissions {
401        Permissions::ReadWrite
402    }
403
404    #[inline(always)]
405    async fn blob_permissions(
406        &self,
407        _creds: &ValidCredentials,
408        _blob: &ImageDigest,
409    ) -> Permissions {
410        Permissions::ReadWrite
411    }
412}
413
414#[async_trait]
415impl AuthProvider for Secret<String> {
416    #[inline(always)]
417    async fn check_credentials(&self, unverified: &Unverified) -> Option<ValidCredentials> {
418        match unverified {
419            Unverified::UsernameAndPassword {
420                username: _,
421                password,
422            } => {
423                if constant_time_eq::constant_time_eq(
424                    password.reveal().as_bytes(),
425                    self.reveal().as_bytes(),
426                ) {
427                    Some(ValidCredentials::new(()))
428                } else {
429                    None
430                }
431            }
432            Unverified::NoCredentials => None,
433        }
434    }
435
436    #[inline(always)]
437    async fn image_permissions(
438        &self,
439        _creds: &ValidCredentials,
440        _image: &ImageLocation,
441    ) -> Permissions {
442        Permissions::ReadWrite
443    }
444
445    #[inline(always)]
446    async fn blob_permissions(
447        &self,
448        _creds: &ValidCredentials,
449        _blob: &ImageDigest,
450    ) -> Permissions {
451        Permissions::ReadWrite
452    }
453}