-
Notifications
You must be signed in to change notification settings - Fork 8
Wire request signing to RuntimeServices store primitives (PR 9) #609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
prk-Jr
wants to merge
88
commits into
main
Choose a base branch
from
feature/edgezero-pr9-wire-signing-to-store-primitives
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 80 commits
Commits
Show all changes
88 commits
Select commit
Hold shift + click to select a range
180471e
Rename crates to trusted-server-core and trusted-server-adapter-fastly
prk-Jr f63e5b2
Add platform abstraction layer with traits and RuntimeServices
prk-Jr 020e88c
Merge remote-tracking branch 'origin/main' into feature/edgezero-pr1-…
prk-Jr 37c8fbf
Merge branch 'feature/edgezero-pr1-crate-rename' into feature/edgezer…
prk-Jr 7495d96
Merge branch 'main' into feature/edgezero-pr2-platform-traits
prk-Jr 2c40d58
Address platform layer review feedback
prk-Jr 46e3360
Reject host strings containing control characters in BackendConfig
prk-Jr 2f40b4c
Fix clippy error
prk-Jr 8210a85
Validate scheme and host for control characters in BackendConfig
prk-Jr 99d7bee
Address review findings on platform abstraction layer
prk-Jr a2597e5
Address review findings on platform abstraction layer
prk-Jr d7a35a1
Merge branch 'main' into feature/edgezero-pr2-platform-traits
prk-Jr d8b267b
Add config store read path and storage module split
prk-Jr 591b9b3
Merge branch 'main' into feature/edgezero-pr2-platform-traits
aram356 ce456a9
Merge branch 'main' into feature/edgezero-pr3-config-store
prk-Jr ed57b14
Merge branch 'main' into feature/edgezero-pr3-config-store
prk-Jr a8c5648
Harden legacy config-store reads and align Fastly adapter stubs
prk-Jr 14e54c4
Address storage review feedback
prk-Jr c682c6d
Resolved github-advanced-security bot problems
prk-Jr eec34fb
Address PR review feedback on platform abstraction layer
prk-Jr d6be0b2
Merge branch 'main' into feature/edgezero-pr2-platform-traits
prk-Jr b25bfd6
Add PR 4 design spec for secret store trait (read-only)
prk-Jr a641eb0
Clarify test scope and deferred branches in PR 4 spec
prk-Jr 1ee695c
Add implementation plan for PR 4 secret store trait
prk-Jr 5b205bb
Add test for get_secret_bytes open-failure path
prk-Jr df6bc60
Add NotImplemented tests for FastlyPlatformSecretStore write stubs
prk-Jr 21ec187
Inline StoreId binding and add section comment in write-stub tests
prk-Jr e51a7d6
Remove plan
prk-Jr b4bda32
Add PR 6 design spec for backend and HTTP client traits
prk-Jr 57d6bec
Address spec review findings on PR 6 design
prk-Jr 571656c
Implement PlatformHttpClient and thread RuntimeServices through proxy…
prk-Jr e271dce
Merge branch 'main' into feature/edgezero-pr6-backend-http-client
prk-Jr 7181a92
Merge branch 'main' into feature/edgezero-pr3-config-store
prk-Jr f4c4b57
Merge branch 'main' into feature/edgezero-pr2-platform-traits
prk-Jr b8c4daf
Merge branch 'main' into feature/edgezero-pr4-secret-store
prk-Jr 2bc167e
Merge branch 'main' into feature/edgezero-pr2-platform-traits
prk-Jr b458d64
Address pr review findings
prk-Jr 089a805
Merge branch 'feature/edgezero-pr2-platform-traits' into feature/edge…
prk-Jr 882fd29
Merge branch 'feature/edgezero-pr3-config-store' into feature/edgezer…
prk-Jr 291ad66
Merge branch 'feature/edgezero-pr4-secret-store' into feature/edgezer…
prk-Jr ebf129b
Resolve pr review findings
prk-Jr 2ff0ce9
Add PR7 design spec for geo lookup + client info extract-once
prk-Jr ead539c
Fix spec review issues in PR7 design doc
prk-Jr 8bbfc74
Update PR7 spec to address all five agent review findings
prk-Jr b39cd79
Add PR7 implementation plan and address plan review findings
prk-Jr d6a624a
Fix three plan review findings and two open questions
prk-Jr 986a1b2
Broaden two low-severity doc cleanup steps in PR7 plan
prk-Jr 86079c5
Fix two remaining low findings in PR7 plan
prk-Jr a03a765
Fix count drift in Step 7: four → five locations
prk-Jr ac79961
Add client_info field to AuctionContext and fix all construction sites
prk-Jr b96aec0
Change RequestInfo::from_request to take &ClientInfo, thread services…
prk-Jr 661e3df
Add Task 2 follow-up coverage and README route fixes
prk-Jr 774a07f
Add services param to generate_synthetic_id, remove Fastly IP/geo cal…
prk-Jr 95ce45e
Revert premature publisher geo change from Task 3
prk-Jr b10dcec
Replace deprecated GeoInfo::from_request in publisher.rs with service…
prk-Jr 888170d
Remove Fastly IP extraction from Didomi copy_headers, use ClientInfo …
prk-Jr f856b68
Move IpAddr import to test module level in didomi.rs
prk-Jr eb12522
Apply rustfmt formatting to didomi.rs, publisher.rs, and synthetic.rs
prk-Jr 7fcb3b4
Add test coverage for generate_synthetic_id with concrete client IP
prk-Jr 1844290
Align geo lookup warn log format with codebase convention ({e} not {e…
prk-Jr 0132a36
Apply Prettier formatting to PR7 plan and spec docs
prk-Jr ffa1174
Document content rewriting as platform-agnostic in platform module
prk-Jr fbbf767
Document html_processor as platform-agnostic
prk-Jr b89a9e6
Document streaming_processor as platform-agnostic
prk-Jr 6fa8b38
Fix unresolved doc link: replace EdgeRequest with edgezero_core::http…
prk-Jr e9ce63d
Add plan for content rewriting
prk-Jr 794b66d
Add plan for PR9: wire signing to store primitives
prk-Jr e13537b
Add build_services_with_config_and_secret to test_support
prk-Jr b0c6571
Merge branch 'main' into feature/edgezero-pr6-backend-http-client
prk-Jr 14f282b
Merge branch 'feature/edgezero-pr6-backend-http-client' into feature/…
prk-Jr 34c44bd
Merge branch 'feature/edgezero-pr7-geo-client-info' into feature/edge…
prk-Jr 04b9cda
Merge branch 'feature/edgezero-pr8-content-rewriting' into feature/ed…
prk-Jr 2c0c4eb
Add FastlyManagementApiClient to adapter
prk-Jr f6b00c8
Implement FastlyPlatformConfigStore and FastlyPlatformSecretStore wri…
prk-Jr ec62970
Migrate KeyRotationManager from FastlyApiClient to RuntimeServices st…
prk-Jr 27a0949
Migrate signing.rs from FastlyConfigStore/FastlySecretStore to Runtim…
prk-Jr 5b6555f
Delete storage/api_client.rs from core; remove FastlyApiClient
prk-Jr 0a8915c
Fix formatting after CI gate check
prk-Jr 2f1cc97
Add services to AuctionContext; remove deprecated from_config shim
prk-Jr ba141fa
Fix prettier formatting in PR9 plan document
prk-Jr 1a0c0b6
Address PR findings
prk-Jr 7365ec4
Address PR review findings
prk-Jr 079a97f
Address review findings
prk-Jr 3924a98
Address review findings
prk-Jr d5f5c0d
Fix rotate/delete atomicity, HTTP verb, idempotent deletes, and weak …
prk-Jr f9df8da
Merge branch 'main' into feature/edgezero-pr9-wire-signing-to-store-p…
prk-Jr 1a2cb46
Address round-3 review findings
prk-Jr 9a1fd41
Resolve PR review findings
prk-Jr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
295 changes: 295 additions & 0 deletions
295
crates/trusted-server-adapter-fastly/src/management_api.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,295 @@ | ||
| //! Fastly management API transport for store write operations. | ||
| //! | ||
| //! Provides [`FastlyManagementApiClient`], which wraps the Fastly REST | ||
| //! management API for write operations on config and secret stores. | ||
| //! Used by [`super::platform::FastlyPlatformConfigStore`] and | ||
| //! [`super::platform::FastlyPlatformSecretStore`] to back store write methods. | ||
| //! | ||
| //! # Credentials | ||
| //! | ||
| //! The Fastly API token is read from the `api-keys` secret store under the | ||
| //! `api_key` entry. The token must have config-store write and secret-store | ||
| //! write permissions only — no service-level admin or purge permissions. | ||
| //! | ||
| //! # Security | ||
| //! | ||
| //! Credential values are never logged. Log messages include store IDs and | ||
| //! operation names only. | ||
|
|
||
| use std::io::Read; | ||
|
|
||
| use error_stack::{Report, ResultExt}; | ||
| use fastly::http::StatusCode; | ||
| use fastly::{Request, Response}; | ||
| use trusted_server_core::platform::{PlatformError, PlatformSecretStore, StoreName}; | ||
|
|
||
| use crate::platform::FastlyPlatformSecretStore; | ||
|
|
||
| const FASTLY_API_HOST: &str = "https://api.fastly.com"; | ||
| const API_KEYS_STORE: &str = "api-keys"; | ||
| const API_KEY_ENTRY: &str = "api_key"; | ||
|
|
||
| pub(crate) fn build_config_item_payload(value: &str) -> String { | ||
| format!("item_value={}", urlencoding::encode(value)) | ||
| } | ||
|
|
||
| /// HTTP client for Fastly management API write operations. | ||
| /// | ||
| /// Backs the `put`/`delete` methods of [`FastlyPlatformConfigStore`] and | ||
| /// the `create`/`delete` methods of [`FastlyPlatformSecretStore`]. | ||
| pub(crate) struct FastlyManagementApiClient { | ||
| api_key: Vec<u8>, | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
| base_url: &'static str, | ||
| backend_name: String, | ||
| } | ||
|
|
||
| impl FastlyManagementApiClient { | ||
| /// Initialize the client by reading the API token from the `api-keys` secret store. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns [`PlatformError::Backend`] if the management API backend cannot | ||
| /// be registered, or [`PlatformError::SecretStore`] if the API key cannot | ||
| /// be read. | ||
| pub(crate) fn new() -> Result<Self, Report<PlatformError>> { | ||
| use trusted_server_core::backend::BackendConfig; | ||
|
|
||
| let backend_name = BackendConfig::from_url(FASTLY_API_HOST, true) | ||
| .change_context(PlatformError::Backend) | ||
| .attach("failed to register Fastly management API backend")?; | ||
|
|
||
| let api_key = FastlyPlatformSecretStore | ||
| .get_bytes(&StoreName::from(API_KEYS_STORE), API_KEY_ENTRY) | ||
| .change_context(PlatformError::SecretStore) | ||
| .attach("failed to read Fastly API key from secret store")?; | ||
|
|
||
| log::debug!("FastlyManagementApiClient: initialized for management API operations"); | ||
|
|
||
| Ok(Self { | ||
| api_key, | ||
| base_url: FASTLY_API_HOST, | ||
| backend_name, | ||
| }) | ||
| } | ||
|
|
||
| fn make_request( | ||
| &self, | ||
| method: &str, | ||
| path: &str, | ||
| body: Option<String>, | ||
| content_type: &str, | ||
| ) -> Result<Response, Report<PlatformError>> { | ||
| let url = format!("{}{}", self.base_url, path); | ||
| let api_key_str = String::from_utf8_lossy(&self.api_key).to_string(); | ||
|
|
||
| let mut request = match method { | ||
| "GET" => Request::get(&url), | ||
| "POST" => Request::post(&url), | ||
| "PUT" => Request::put(&url), | ||
| "DELETE" => Request::delete(&url), | ||
| _ => { | ||
| return Err(Report::new(PlatformError::ConfigStore) | ||
| .attach(format!("unsupported HTTP method: {}", method))) | ||
| } | ||
| }; | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
|
|
||
| request = request | ||
| .with_header("Fastly-Key", api_key_str) | ||
| .with_header("Accept", "application/json"); | ||
|
|
||
| if let Some(body_content) = body { | ||
| request = request | ||
| .with_header("Content-Type", content_type) | ||
| .with_body(body_content); | ||
| } | ||
|
|
||
| request.send(&self.backend_name).map_err(|e| { | ||
| Report::new(PlatformError::ConfigStore) | ||
| .attach(format!("management API request failed: {}", e)) | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
| }) | ||
| } | ||
|
|
||
| /// Update or create a config store item. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if the API request fails or returns a non-OK status. | ||
| pub(crate) fn update_config_item( | ||
| &self, | ||
| store_id: &str, | ||
| key: &str, | ||
| value: &str, | ||
| ) -> Result<(), Report<PlatformError>> { | ||
| let path = format!("/resources/stores/config/{}/item/{}", store_id, key); | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
| let payload = build_config_item_payload(value); | ||
|
|
||
| let mut response = self.make_request( | ||
| "PUT", | ||
| &path, | ||
| Some(payload), | ||
| "application/x-www-form-urlencoded", | ||
| )?; | ||
|
|
||
| let mut buf = String::new(); | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
| response | ||
| .get_body_mut() | ||
| .read_to_string(&mut buf) | ||
| .change_context(PlatformError::ConfigStore)?; | ||
|
|
||
| if response.get_status() == StatusCode::OK { | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
| log::debug!( | ||
| "FastlyManagementApiClient: updated config key '{}' in store '{}'", | ||
| key, | ||
| store_id | ||
| ); | ||
| Ok(()) | ||
| } else { | ||
| Err(Report::new(PlatformError::ConfigStore).attach(format!( | ||
| "config item update failed with HTTP {} for key '{}' in store '{}'", | ||
| response.get_status(), | ||
| key, | ||
| store_id | ||
| ))) | ||
| } | ||
| } | ||
|
|
||
| /// Delete a config store item. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if the API request fails or returns an unexpected status. | ||
| pub(crate) fn delete_config_item( | ||
| &self, | ||
| store_id: &str, | ||
| key: &str, | ||
| ) -> Result<(), Report<PlatformError>> { | ||
| let path = format!("/resources/stores/config/{}/item/{}", store_id, key); | ||
|
|
||
| let mut response = self.make_request("DELETE", &path, None, "application/json")?; | ||
|
|
||
| let mut buf = String::new(); | ||
| response | ||
| .get_body_mut() | ||
| .read_to_string(&mut buf) | ||
| .change_context(PlatformError::ConfigStore)?; | ||
|
|
||
| if response.get_status() == StatusCode::OK | ||
| || response.get_status() == StatusCode::NO_CONTENT | ||
| { | ||
| log::debug!( | ||
| "FastlyManagementApiClient: deleted config key '{}' from store '{}'", | ||
| key, | ||
| store_id | ||
| ); | ||
| Ok(()) | ||
| } else { | ||
| Err(Report::new(PlatformError::ConfigStore).attach(format!( | ||
| "config item delete failed with HTTP {} for key '{}' in store '{}'", | ||
| response.get_status(), | ||
| key, | ||
| store_id | ||
| ))) | ||
| } | ||
| } | ||
|
|
||
| /// Create or overwrite a secret store entry. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if the API request fails or returns a non-OK status. | ||
| pub(crate) fn create_secret( | ||
| &self, | ||
| store_id: &str, | ||
| secret_name: &str, | ||
| secret_value: &str, | ||
| ) -> Result<(), Report<PlatformError>> { | ||
| let path = format!("/resources/stores/secret/{}/secrets", store_id); | ||
| let payload = serde_json::json!({ | ||
| "name": secret_name, | ||
| "secret": secret_value | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
| }); | ||
|
|
||
| let mut response = | ||
| self.make_request("POST", &path, Some(payload.to_string()), "application/json")?; | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
|
|
||
| let mut buf = String::new(); | ||
| response | ||
| .get_body_mut() | ||
| .read_to_string(&mut buf) | ||
| .change_context(PlatformError::SecretStore)?; | ||
|
|
||
| if response.get_status() == StatusCode::OK { | ||
|
prk-Jr marked this conversation as resolved.
Outdated
|
||
| log::debug!( | ||
| "FastlyManagementApiClient: created secret '{}' in store '{}'", | ||
| secret_name, | ||
| store_id | ||
| ); | ||
| Ok(()) | ||
| } else { | ||
| Err(Report::new(PlatformError::SecretStore).attach(format!( | ||
| "secret create failed with HTTP {} for name '{}' in store '{}'", | ||
| response.get_status(), | ||
| secret_name, | ||
| store_id | ||
| ))) | ||
| } | ||
| } | ||
|
|
||
| /// Delete a secret store entry. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if the API request fails or returns an unexpected status. | ||
| pub(crate) fn delete_secret( | ||
| &self, | ||
| store_id: &str, | ||
| secret_name: &str, | ||
| ) -> Result<(), Report<PlatformError>> { | ||
| let path = format!( | ||
| "/resources/stores/secret/{}/secrets/{}", | ||
| store_id, secret_name | ||
| ); | ||
|
|
||
| let mut response = self.make_request("DELETE", &path, None, "application/json")?; | ||
|
|
||
| let mut buf = String::new(); | ||
| response | ||
| .get_body_mut() | ||
| .read_to_string(&mut buf) | ||
| .change_context(PlatformError::SecretStore)?; | ||
|
|
||
| if response.get_status() == StatusCode::OK | ||
| || response.get_status() == StatusCode::NO_CONTENT | ||
| { | ||
| log::debug!( | ||
| "FastlyManagementApiClient: deleted secret '{}' from store '{}'", | ||
| secret_name, | ||
| store_id | ||
| ); | ||
| Ok(()) | ||
| } else { | ||
| Err(Report::new(PlatformError::SecretStore).attach(format!( | ||
| "secret delete failed with HTTP {} for name '{}' in store '{}'", | ||
| response.get_status(), | ||
| secret_name, | ||
| store_id | ||
| ))) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn build_config_item_payload_url_encodes_reserved_characters() { | ||
| let payload = build_config_item_payload(r#"value with spaces + symbols &= {"kid":"a+b"}"#); | ||
|
|
||
| assert_eq!( | ||
| payload, | ||
| "item_value=value%20with%20spaces%20%2B%20symbols%20%26%3D%20%7B%22kid%22%3A%22a%2Bb%22%7D", | ||
| "should URL-encode config item values in form payloads" | ||
| ); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.