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