Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/common/src/request_signing/jwks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rand::rngs::OsRng;

use crate::error::TrustedServerError;
use crate::fastly_storage::FastlyConfigStore;
use crate::request_signing::JWKS_CONFIG_STORE_NAME;

pub struct Keypair {
pub signing_key: SigningKey,
Expand Down Expand Up @@ -60,7 +61,7 @@ impl Keypair {
///
/// Returns an error if the config store cannot be accessed or if active keys cannot be retrieved.
pub fn get_active_jwks() -> Result<String, Report<TrustedServerError>> {
let store = FastlyConfigStore::new("jwks_store");
let store = FastlyConfigStore::new(JWKS_CONFIG_STORE_NAME);
let active_kids_str = store
.get("active-kids")
.attach("while fetching active kids list")?;
Expand Down
24 changes: 24 additions & 0 deletions crates/common/src/request_signing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,37 @@
//!
//! This module provides cryptographic signing capabilities using Ed25519 keys,
//! including JWKS management, key rotation, and signature verification.
//!
//! # Store names vs store IDs
//!
//! Fastly stores have two identifiers:
//!
//! - **Store name** ([`JWKS_CONFIG_STORE_NAME`], [`SIGNING_SECRET_STORE_NAME`]):
//! used at the edge for reads via `ConfigStore::open` / `SecretStore::open`.
//! These are configured in `fastly.toml`.
//!
//! - **Store ID** (`RequestSigning::config_store_id`, `RequestSigning::secret_store_id`):
//! used by the Fastly management API for writes (creating, updating, and
//! deleting items). These are set in `trusted-server.toml`.

pub mod discovery;
pub mod endpoints;
pub mod jwks;
pub mod rotation;
pub mod signing;

/// Config store name for JWKS public keys (edge reads via `ConfigStore::open`).
///
/// This must match the store name declared in `fastly.toml` under
/// `[local_server.config_stores]`.
pub const JWKS_CONFIG_STORE_NAME: &str = "jwks_store";

/// Secret store name for Ed25519 signing keys (edge reads via `SecretStore::open`).
///
/// This must match the store name declared in `fastly.toml` under
/// `[local_server.secret_stores]`.
pub const SIGNING_SECRET_STORE_NAME: &str = "signing_keys";

pub use discovery::*;
pub use endpoints::*;
pub use jwks::*;
Expand Down
19 changes: 14 additions & 5 deletions crates/common/src/request_signing/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use jose_jwk::Jwk;

use crate::error::TrustedServerError;
use crate::fastly_storage::{FastlyApiClient, FastlyConfigStore};
use crate::request_signing::JWKS_CONFIG_STORE_NAME;

use super::Keypair;

Expand All @@ -22,15 +23,23 @@ pub struct KeyRotationResult {
}

pub struct KeyRotationManager {
/// Edge-side config store for reading JWKS (uses store name).
config_store: FastlyConfigStore,
/// Management API client for writing to stores (uses store IDs).
api_client: FastlyApiClient,
/// Fastly API store ID for config store writes.
config_store_id: String,
/// Fastly API store ID for secret store writes.
secret_store_id: String,
}

impl KeyRotationManager {
/// Creates a new key rotation manager.
///
/// The `config_store_id` and `secret_store_id` are Fastly management API
/// identifiers used for write operations. Edge reads use the store names
/// defined in [`JWKS_CONFIG_STORE_NAME`] and [`crate::request_signing::SIGNING_SECRET_STORE_NAME`].
///
/// # Errors
///
/// Returns an error if the API client cannot be initialized.
Expand All @@ -41,7 +50,7 @@ impl KeyRotationManager {
let config_store_id = config_store_id.into();
let secret_store_id = secret_store_id.into();

let config_store = FastlyConfigStore::new("jwks_store");
let config_store = FastlyConfigStore::new(JWKS_CONFIG_STORE_NAME);
let api_client = FastlyApiClient::new()?;

Ok(Self {
Expand Down Expand Up @@ -218,11 +227,11 @@ mod tests {

#[test]
fn test_key_rotation_manager_creation() {
let result = KeyRotationManager::new("jwks_store", "signing_keys");
let result = KeyRotationManager::new("test-config-store-id", "test-secret-store-id");
match result {
Ok(manager) => {
assert_eq!(manager.config_store_id, "jwks_store");
assert_eq!(manager.secret_store_id, "signing_keys");
assert_eq!(manager.config_store_id, "test-config-store-id");
assert_eq!(manager.secret_store_id, "test-secret-store-id");
}
Err(e) => {
println!("Expected error in test environment: {}", e);
Expand All @@ -232,7 +241,7 @@ mod tests {

#[test]
fn test_list_active_keys() {
let result = KeyRotationManager::new("jwks_store", "signing_keys");
let result = KeyRotationManager::new("test-config-store-id", "test-secret-store-id");
if let Ok(manager) = result {
match manager.list_active_keys() {
Ok(keys) => {
Expand Down
9 changes: 5 additions & 4 deletions crates/common/src/request_signing/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ use serde::Serialize;

use crate::error::TrustedServerError;
use crate::fastly_storage::{FastlyConfigStore, FastlySecretStore};
use crate::request_signing::{JWKS_CONFIG_STORE_NAME, SIGNING_SECRET_STORE_NAME};

/// Retrieves the current active key ID from the config store.
///
/// # Errors
///
/// Returns an error if the config store cannot be accessed or the current-kid key is not found.
pub fn get_current_key_id() -> Result<String, Report<TrustedServerError>> {
let store = FastlyConfigStore::new("jwks_store");
let store = FastlyConfigStore::new(JWKS_CONFIG_STORE_NAME);
store.get("current-kid")
}

Expand Down Expand Up @@ -119,15 +120,15 @@ impl RequestSigner {
///
/// Returns an error if the key ID cannot be retrieved or the key cannot be parsed.
pub fn from_config() -> Result<Self, Report<TrustedServerError>> {
let config_store = FastlyConfigStore::new("jwks_store");
let config_store = FastlyConfigStore::new(JWKS_CONFIG_STORE_NAME);
let key_id =
config_store
.get("current-kid")
.change_context(TrustedServerError::Configuration {
message: "Failed to get current-kid".into(),
})?;

let secret_store = FastlySecretStore::new("signing_keys");
let secret_store = FastlySecretStore::new(SIGNING_SECRET_STORE_NAME);
let key_bytes = secret_store
.get(&key_id)
.attach(format!("Failed to get signing key for kid: {}", key_id))?;
Expand Down Expand Up @@ -177,7 +178,7 @@ pub fn verify_signature(
signature_b64: &str,
kid: &str,
) -> Result<bool, Report<TrustedServerError>> {
let store = FastlyConfigStore::new("jwks_store");
let store = FastlyConfigStore::new(JWKS_CONFIG_STORE_NAME);
let jwk_json = store
.get(kid)
.change_context(TrustedServerError::Configuration {
Expand Down