From 3c2adeeb8e24798a42c18ecdeab21b0b731809cd Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 6 Jan 2026 09:23:17 -0600 Subject: [PATCH 1/6] remove hardcoded backend name, fixes key rotation with fastly --- crates/common/src/fastly_storage.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index c4a8241..6034eba 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -76,6 +76,7 @@ impl FastlySecretStore { pub struct FastlyApiClient { api_key: Vec, base_url: String, + backend_name: String, } impl FastlyApiClient { @@ -84,7 +85,7 @@ impl FastlyApiClient { } pub fn from_secret_store(store_name: &str, key_name: &str) -> Result { - ensure_backend_from_url("https://api.fastly.com").map_err(|e| { + let backend_name = ensure_backend_from_url("https://api.fastly.com").map_err(|e| { TrustedServerError::Configuration { message: format!("Failed to ensure API backend: {}", e), } @@ -93,9 +94,15 @@ impl FastlyApiClient { let secret_store = FastlySecretStore::new(store_name); let api_key = secret_store.get(key_name)?; + log::info!( + "FastlyApiClient initialized with backend: {}", + backend_name + ); + Ok(Self { api_key, base_url: "https://api.fastly.com".to_string(), + backend_name, }) } @@ -132,7 +139,7 @@ impl FastlyApiClient { .with_body(body_content); } - request.send("backend_https_api_fastly_com").map_err(|e| { + request.send(&self.backend_name).map_err(|e| { TrustedServerError::Configuration { message: format!("Failed to send API request: {}", e), } From 42a7afe56a58de5d4b874166c917dfdc56b51298 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 6 Jan 2026 09:40:11 -0600 Subject: [PATCH 2/6] update key rotation docs --- docs/guide/key-rotation.md | 180 ++++++++++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 4 deletions(-) diff --git a/docs/guide/key-rotation.md b/docs/guide/key-rotation.md index c191fb9..b5b3547 100644 --- a/docs/guide/key-rotation.md +++ b/docs/guide/key-rotation.md @@ -26,6 +26,174 @@ Key rotation is the process of generating new signing keys and transitioning fro - **Incident-based**: Immediately if compromise suspected - **Before major releases**: Ensure fresh keys for new deployments +## Prerequisites + +Before you can rotate keys, you need to set up the required Fastly stores and API credentials. + +### Required Stores + +Key rotation requires three Fastly stores: + +1. **Config Store** (`jwks_store`) - Stores public JWKs and metadata + - `current-kid` - The active key identifier + - `active-kids` - Comma-separated list of valid key IDs + - Individual JWKs keyed by their `kid` + +2. **Secret Store** (`signing_keys`) - Stores private signing keys + - Each key stored with its `kid` as the key name + - Values are base64-encoded Ed25519 private keys + +3. **Secret Store** (`api-keys`) - Stores Fastly API credentials + - `api_key` - Fastly API token for managing stores + +### Creating Stores + +#### 1. Create Config Store + +```bash +# Create the config store +fastly config-store create --name=jwks_store + +# Get the store ID (you'll need this for configuration) +fastly config-store list +``` + +Note the Config Store ID from the output (e.g., `r8loXvD9GESm5Crw5oyS16`). + +#### 2. Create Secret Stores + +```bash +# Create secret store for signing keys +fastly secret-store create --name=signing_keys + +# Create secret store for API credentials +fastly secret-store create --name=api-keys + +# Get the store IDs +fastly secret-store list +``` + +Note both Secret Store IDs from the output. + +::: tip Dashboard Alternative +You can also create stores via the Fastly dashboard, but CLI commands are recommended for automation and reproducibility. +::: + +### Creating Fastly API Key + +Key rotation uses the Fastly API to manage store contents. You need to create an API token: + +#### Step 1: Generate API Token + +1. Log in to the [Fastly Dashboard](https://manage.fastly.com) +2. Navigate to **Account → API Tokens → Personal Tokens** +3. Click **Create Token** +4. Configure the token: + - **Name**: `trusted-server-key-rotation` + - **Scope**: `global:read`, `global:write` (or scope to your specific service) + - **Expiration**: Set according to your security policy + +#### Step 2: Store API Token + +Store the API token in the `api-keys` secret store: + +```bash +# Store the API key +fastly secret-store-entry create \ + --store-id= \ + --name=api_key \ + --secret= +``` + +::: warning Keep Your API Token Secure +- Never commit API tokens to version control +- Store them only in Fastly Secret Store +- Rotate API tokens according to your security policy +- Use minimal required permissions +::: + +### Linking Stores to Service + +Stores must be linked to your Compute service to be accessible at runtime. + +#### Production (CLI) + +```bash +# Link config store +fastly service-version compute config-store create \ + --version= \ + --config-store-id= \ + --name=jwks_store + +# Link signing keys secret store +fastly service-version compute secret-store create \ + --version= \ + --secret-store-id= \ + --name=signing_keys + +# Link API keys secret store +fastly service-version compute secret-store create \ + --version= \ + --secret-store-id= \ + --name=api-keys +``` + +::: tip Dashboard Linking +You can also link stores via the Fastly dashboard under your service's **Resources** section. +::: + +#### Local Development + +For local testing, configure stores in `fastly.toml`: + +```toml +[local_server.config_stores] + [local_server.config_stores.jwks_store] + format = "inline-toml" + [local_server.config_stores.jwks_store.contents] + ts-2025-01-01 = "{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"kid\":\"ts-2025-01-01\",\"use\":\"sig\",\"x\":\"...\"}" + current-kid = "ts-2025-01-01" + active-kids = "ts-2025-01-01" + +[local_server.secret_stores] + [[local_server.secret_stores.signing_keys]] + key = "ts-2025-01-01" + data = "NVnTYrw5xoyTJDOwoUWoPJO3A6UCCXOJJUzgGTxxx7k=" + + [[local_server.secret_stores.api-keys]] + key = "api_key" + env = "FASTLY_KEY" # Load from environment variable +``` + +### Configuration in trusted-server.toml + +Update `trusted-server.toml` with your store IDs: + +```toml +[request_signing] +enabled = true +config_store_id = "r8loXvD9GESm5Crw5oyS16" # Your jwks_store ID +secret_store_id = "5D6Ps10nXkaWpbgbxh1mVJ" # Your signing_keys ID +``` + +::: tip Getting Store IDs +Use `fastly config-store list` and `fastly secret-store list` to retrieve your store IDs. +::: + +### Verification + +Verify your setup is correct: + +```bash +# Test local development +fastly compute serve + +# Check that stores are accessible +curl http://localhost:7676/.well-known/trusted-server.json +``` + +You should see a JWKS response with your public keys. + ## Key Rotation Process ### Architecture @@ -405,9 +573,11 @@ Test rotation in staging first: **Error**: `Failed to create KeyRotationManager` **Solutions**: -- Check Fastly API token is configured -- Verify config_store_id and secret_store_id settings -- Ensure stores exist in Fastly dashboard +- Verify all required stores are created (see [Prerequisites](#prerequisites)) +- Check Fastly API token is stored in `api-keys` secret store as `api_key` +- Verify `config_store_id` and `secret_store_id` in `trusted-server.toml` match your actual store IDs +- Ensure stores are linked to your Compute service +- Confirm API token has `global:read` and `global:write` permissions ### Cannot Deactivate Key @@ -476,6 +646,8 @@ Restrict rotation endpoints: ## Next Steps +- Complete the [Prerequisites](#prerequisites) setup if you haven't already - Learn about [Request Signing](/guide/request-signing) for using keys -- Review [Configuration](/guide/configuration) for store setup +- Review [Configuration](/guide/configuration) for additional store setup - Set up [Testing](/guide/testing) for rotation procedures +- Read about [GDPR Compliance](/guide/gdpr-compliance) for privacy considerations From 5b7392e1de3b53f5bd3f2108aad8426c3ca7b763 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 6 Jan 2026 09:45:12 -0600 Subject: [PATCH 3/6] cargo fmt --- crates/common/src/fastly_storage.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index 6034eba..4d74116 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -94,10 +94,7 @@ impl FastlyApiClient { let secret_store = FastlySecretStore::new(store_name); let api_key = secret_store.get(key_name)?; - log::info!( - "FastlyApiClient initialized with backend: {}", - backend_name - ); + log::info!("FastlyApiClient initialized with backend: {}", backend_name); Ok(Self { api_key, @@ -139,11 +136,11 @@ impl FastlyApiClient { .with_body(body_content); } - request.send(&self.backend_name).map_err(|e| { - TrustedServerError::Configuration { + request + .send(&self.backend_name) + .map_err(|e| TrustedServerError::Configuration { message: format!("Failed to send API request: {}", e), - } - }) + }) } pub fn update_config_item( From 32d107808a4103ba347ae118beb39a9bf3b66185 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 22 Jan 2026 11:39:14 -0600 Subject: [PATCH 4/6] changes fastly api host to const and remove ids / keys from examples in docs --- crates/common/src/fastly_storage.rs | 8 +++++--- docs/guide/key-rotation.md | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index 4d74116..bf34598 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -6,6 +6,8 @@ use http::StatusCode; use crate::backend::ensure_backend_from_url; use crate::error::TrustedServerError; +const FASTLY_API_HOST: &'static str = "https://api.fastly.com"; + pub struct FastlyConfigStore { store_name: String, } @@ -75,7 +77,7 @@ impl FastlySecretStore { pub struct FastlyApiClient { api_key: Vec, - base_url: String, + base_url: &'static str, backend_name: String, } @@ -85,7 +87,7 @@ impl FastlyApiClient { } pub fn from_secret_store(store_name: &str, key_name: &str) -> Result { - let backend_name = ensure_backend_from_url("https://api.fastly.com").map_err(|e| { + let backend_name = ensure_backend_from_url(FASTLY_API_HOST).map_err(|e| { TrustedServerError::Configuration { message: format!("Failed to ensure API backend: {}", e), } @@ -98,7 +100,7 @@ impl FastlyApiClient { Ok(Self { api_key, - base_url: "https://api.fastly.com".to_string(), + base_url: FASTLY_API_HOST, backend_name, }) } diff --git a/docs/guide/key-rotation.md b/docs/guide/key-rotation.md index b5b3547..738852e 100644 --- a/docs/guide/key-rotation.md +++ b/docs/guide/key-rotation.md @@ -58,7 +58,7 @@ fastly config-store create --name=jwks_store fastly config-store list ``` -Note the Config Store ID from the output (e.g., `r8loXvD9GESm5Crw5oyS16`). +Note the Config Store ID from the output. #### 2. Create Secret Stores @@ -158,7 +158,7 @@ For local testing, configure stores in `fastly.toml`: [local_server.secret_stores] [[local_server.secret_stores.signing_keys]] key = "ts-2025-01-01" - data = "NVnTYrw5xoyTJDOwoUWoPJO3A6UCCXOJJUzgGTxxx7k=" + data = "" [[local_server.secret_stores.api-keys]] key = "api_key" @@ -172,8 +172,8 @@ Update `trusted-server.toml` with your store IDs: ```toml [request_signing] enabled = true -config_store_id = "r8loXvD9GESm5Crw5oyS16" # Your jwks_store ID -secret_store_id = "5D6Ps10nXkaWpbgbxh1mVJ" # Your signing_keys ID +config_store_id = "" # Your jwks_store ID +secret_store_id = " Date: Mon, 26 Jan 2026 10:07:47 -0600 Subject: [PATCH 5/6] fix clippy warning --- crates/common/src/fastly_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index bf34598..a2dfd14 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -6,7 +6,7 @@ use http::StatusCode; use crate::backend::ensure_backend_from_url; use crate::error::TrustedServerError; -const FASTLY_API_HOST: &'static str = "https://api.fastly.com"; +const FASTLY_API_HOST: &str = "https://api.fastly.com"; pub struct FastlyConfigStore { store_name: String, From 90f7f586cb3ddc62fa35a87b4e8fd631a12c26d7 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 26 Jan 2026 10:08:32 -0600 Subject: [PATCH 6/6] change log level --- crates/common/src/fastly_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index a2dfd14..d39170d 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -96,7 +96,7 @@ impl FastlyApiClient { let secret_store = FastlySecretStore::new(store_name); let api_key = secret_store.get(key_name)?; - log::info!("FastlyApiClient initialized with backend: {}", backend_name); + log::debug!("FastlyApiClient initialized with backend: {}", backend_name); Ok(Self { api_key,