Skip to main content

objectstore_server/auth/
key_directory.rs

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