Skip to main content

objectstore_server/auth/
key_directory.rs

1use std::collections::{BTreeMap, HashSet};
2use std::path::Path;
3
4use anyhow::Context;
5use jsonwebtoken::DecodingKey;
6use objectstore_types::auth::Permission;
7
8use crate::config::{AuthZ, AuthZVerificationKey};
9
10async fn read_key_from_file(filename: &Path) -> anyhow::Result<DecodingKey> {
11    let key_content = tokio::fs::read_to_string(filename)
12        .await
13        .with_context(|| format!("reading key from {filename:?}"))?;
14    DecodingKey::from_ed_pem(key_content.as_bytes())
15        .with_context(|| format!("parsing key from {filename:?}"))
16}
17
18/// Configures the EdDSA public key(s) and permissions used to verify tokens from a single `kid`.
19///
20/// Note: [`jsonwebtoken::DecodingKey`] redacts key content in its `Debug` implementation.
21#[derive(Debug)]
22pub struct PublicKeyConfig {
23    /// Versions of this key's key material which may be used to verify signatures.
24    ///
25    /// If a key is being rotated, the old and new versions of that key should both be
26    /// configured so objectstore can verify signatures while the updated key is still
27    /// rolling out. Otherwise, this should only contain the most recent version of a key.
28    pub key_versions: Vec<DecodingKey>,
29
30    /// The maximum set of permissions that this key's signer is authorized to grant.
31    ///
32    /// If a request's auth token grants full permission but it was signed by a key that
33    /// is only allowed to grant read permission, then the request only has read
34    /// permission.
35    pub max_permissions: HashSet<Permission>,
36}
37
38impl PublicKeyConfig {
39    /// Loads key material and permissions from an [`AuthZVerificationKey`] configuration.
40    pub async fn from_config(key_config: &AuthZVerificationKey) -> anyhow::Result<Self> {
41        let mut key_versions = Vec::with_capacity(key_config.key_files.len());
42        for filename in &key_config.key_files {
43            let key = read_key_from_file(filename)
44                .await
45                .inspect_err(|e| objectstore_log::error!("{:?}", e))?;
46            key_versions.push(key);
47        }
48
49        Ok(Self {
50            max_permissions: key_config.max_permissions.clone(),
51            key_versions,
52        })
53    }
54}
55
56/// Directory of keys that may be used to verify a request's auth token.
57///
58/// The auth token is read from the `X-Os-Auth` header (preferred) or the
59/// standard `Authorization` header (fallback). This directory contains a map keyed
60/// on a key's ID. When verifying a JWT, the `kid` field should be read from the
61/// JWT header and used to index into this directory to select the appropriate key.
62#[derive(Debug)]
63pub struct PublicKeyDirectory {
64    /// Mapping from key ID to key configuration.
65    pub keys: BTreeMap<String, PublicKeyConfig>,
66}
67
68impl PublicKeyDirectory {
69    /// Loads the full key directory from an [`AuthZ`] configuration.
70    pub async fn from_config(auth_config: &AuthZ) -> anyhow::Result<Self> {
71        let mut keys = BTreeMap::new();
72        for (kid, key_config) in &auth_config.keys {
73            let config = PublicKeyConfig::from_config(key_config).await?;
74            keys.insert(kid.clone(), config);
75        }
76
77        Ok(Self { keys })
78    }
79}