Skip to content
Draft
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
10 changes: 8 additions & 2 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ on:
push:
branches: [main]
pull_request:
# Keep the lint gates active for the Geo PR stacked on native stores.
branches: [main, feat/native-celld-stores]
# Keep lint gates active for native-store and Geo stacked PRs.
branches: [main, feat/native-celld-stores, feature/pluggable-geo-providers]

permissions:
contents: read
Expand Down Expand Up @@ -118,6 +118,12 @@ jobs:
feature_sets: |-
axum
axum,test-utils
geo-cloudfront
geo-cloudfront,test-utils
geo-maxmind
geo-maxmind,test-utils
geo-cloudfront,geo-maxmind
geo-cloudfront,geo-maxmind,test-utils
steps:
- uses: actions/checkout@v6

Expand Down
91 changes: 87 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ on:
branches: [main]
pull_request:
# Keep checks active for native-store and Geo stacked PRs.
branches: [main, docs/outbound-http-spec, feat/native-celld-stores]
branches:
[
main,
docs/outbound-http-spec,
feat/native-celld-stores,
feature/pluggable-geo-providers,
]

permissions:
contents: read
Expand Down Expand Up @@ -121,6 +127,11 @@ jobs:
- name: Geo dependency isolation
run: bash scripts/check_geo_dependency_contract.sh

- name: Synthetic GeoIP fixture reproducibility
run: |
node --check scripts/generate_geoip_fixtures.mjs
node scripts/generate_geoip_fixtures.mjs --check

- name: Immutable core Geo outcome
run: scripts/run_test_nonzero.sh geo_owned_outcome_survives_public_request_mutation cargo test --offline --locked -p edgezero-core --lib geo_

Expand All @@ -147,9 +158,6 @@ jobs:
- name: Native Geo real-peer hosting (loopback only)
run: scripts/run_test_nonzero.sh geo_hosting_proves_real_peer_and_shared_outcome cargo test --offline --locked -p edgezero-adapter-axum --no-default-features --features geo-cloudfront,test-utils --test production_hosting geo_hosting_proves_real_peer_and_shared_outcome

- name: Native Geo public examples
run: cargo test --offline --locked -p edgezero-adapter-axum --features geo-cloudfront --doc

- name: Native AWS provider and startup contracts (loopback only)
run: |
cargo test --offline --locked -p edgezero-store-aws --no-default-features --features appconfig-agent --lib
Expand Down Expand Up @@ -254,6 +262,81 @@ jobs:
cargo check --locked -p app-demo-adapter-fastly --target wasm32-wasip1 --no-default-features --features fastly
cargo check --locked -p app-demo-adapter-spin --target wasm32-wasip2 --no-default-features --features spin

geo-features:
name: Geo isolated features [${{ matrix.features }}]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- features: axum
cache_key: axum
builtin_sentinel: geo_unavailable_selection_fails_before_runner_resources
- features: geo-cloudfront
cache_key: cloudfront
builtin_sentinel: geo_cloudfront_parsing_matches_spec
- features: geo-maxmind
cache_key: maxmind
builtin_sentinel: geo_maxmind_structural_pass_closes_upstream_data_gaps
- features: geo-cloudfront,geo-maxmind
cache_key: cloudfront-maxmind
builtin_sentinel: geo_maxmind_structural_pass_closes_upstream_data_gaps
steps:
- uses: actions/checkout@v6

- name: Cache Cargo dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-geo-${{ matrix.cache_key }}-${{ hashFiles('**/Cargo.lock') }}

- name: Retrieve Rust version
id: rust-version
run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT"
shell: bash

- name: Set up Rust tool chain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ steps.rust-version.outputs.rust-version }}

- name: Fetch dependencies (locked)
run: cargo fetch --locked

- name: Isolated Geo unit contracts
env:
FEATURES: ${{ matrix.features }}
SENTINEL: ${{ matrix.builtin_sentinel }}
run: |
scripts/run_test_nonzero.sh "$SENTINEL" cargo test --offline --locked -p edgezero-adapter-axum --no-default-features --features "$FEATURES" --lib geo_
scripts/run_test_nonzero.sh geo_reader_admission_uses_private_checked_provenance cargo test --offline --locked -p edgezero-adapter-axum --no-default-features --features "$FEATURES" --lib geo_reader_admission_uses_private_checked_provenance

- name: Supplied reader hosting without required built-ins
env:
FEATURES: ${{ matrix.features }}
run: scripts/run_test_nonzero.sh geo_hosting_supplied_reader_without_builtin cargo test --offline --locked -p edgezero-adapter-axum --no-default-features --features "$FEATURES,test-utils" --test production_hosting geo_hosting_supplied_reader_without_builtin

- name: Disabled MaxMind fails before hosting
if: "!contains(matrix.features, 'geo-maxmind')"
env:
FEATURES: ${{ matrix.features }}
run: scripts/run_test_nonzero.sh geo_hosting_rejects_unavailable_maxmind cargo test --offline --locked -p edgezero-adapter-axum --no-default-features --features "$FEATURES,test-utils" --test production_hosting geo_hosting_rejects_unavailable_maxmind

- name: MaxMind snapshot and restart hosting
if: contains(matrix.features, 'geo-maxmind')
env:
FEATURES: ${{ matrix.features }}
run: scripts/run_test_nonzero.sh geo_hosting_maxmind_snapshot_and_restart cargo test --offline --locked -p edgezero-adapter-axum --no-default-features --features "$FEATURES,test-utils" --test production_hosting geo_hosting_

- name: Public examples with isolated features
env:
FEATURES: ${{ matrix.features }}
run: cargo test --offline --locked -p edgezero-adapter-axum --no-default-features --features "$FEATURES" --doc

adapter-wasm-tests:
name: ${{ matrix.adapter }} wasm tests
runs-on: ubuntu-latest
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

## Unreleased

### Axum local GeoIP and breaking Geo input migration

Draft implementation, not accepted for release. Full metadata-corruption rejection remains blocked
by the pinned reader API, with an explicit ignored regression. File acquisition is Unix-only;
non-Unix selection reports implementation unavailability. Production-size measurements remain open.

- Add default-off `geo-maxmind` for an operator-supplied GeoLite2 City or Country MMDB.
Prepare and validate a bounded, read-only in-memory snapshot before initialization and binding.
Updates require restart; active processes retain their snapshot after source replacement/deletion.
Preparation precedes runtime signal/stop polling and has no filesystem I/O deadline.
- Add explicit `PeerAllowed`/`ForwardedRequired` checked-reader selection and
`SuppliedReaderIp` composition, which works with `axum` alone. No geographic-header fallback,
downloader, remote lookup or automatic permission decision is added.
- Breaking pre-release migration: replace `GeoInput { country, subdivision }` with
`GeoInput::Assertions { country, subdivision }`, match fields by variant, and explicitly handle
or reject `ReaderIp { address, source }`. Update exhaustive matches for resolution `NoMatch`,
unavailable `MissingReaderIp`/`NoMatch`, source `LocalDatabase`/`CheckedReaderIp` and trust basis
`CheckedReaderIp(GeoIpSource)`. Unsupported inputs return `ProviderFailure`.
- Replace capability's `Selected { provider, source, trust_basis }` with
`Selected { provider, input: NativeGeoInputContract }`. Capability validates configuration
without database I/O and does not describe request provenance. Reserve custom provider ID
`maxmind` alongside `cloudfront`. No compatibility aliases or unrelated version bumps.
- Document mounts, proxy trust, redaction, uncertainty, dataset attribution and operator-owned
update/deletion duties, including running snapshots and rollback copies, in the
[native Geo guide](docs/guide/native-geo.md).

### Portable HTTP And Request Lifecycles (PR #275)

The changes below cover [PR #275](https://github.com/stackpop/edgezero/pull/275), including its
Expand Down
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ libc = "0.2"
miniz_oxide = { version = "=0.8.9", default-features = false, features = ["simd", "with-alloc"] }
log-fastly = "=0.13.1"
matchit = "0.9"
maxminddb = { version = "=0.32.0", default-features = false }
once_cell = "1"
redb = "4.1.0"
reqwest = { version = "=0.13.4", default-features = false, features = ["rustls", "blocking", "json"] }
Expand Down
3 changes: 3 additions & 0 deletions crates/edgezero-adapter-axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ aws-dynamodb = ["axum", "edgezero-store-aws/dynamodb-kv"]
celld-config = ["axum", "edgezero-store-celld/config"]
celld-kv = ["axum", "edgezero-store-celld/kv"]
geo-cloudfront = ["axum"]
geo-maxmind = ["axum", "dep:maxminddb", "dep:sha2"]
cli = [
"native-bindings",
"dep:edgezero-adapter",
Expand Down Expand Up @@ -78,6 +79,8 @@ tracing = { workspace = true }
walkdir = { workspace = true, optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
maxminddb = { workspace = true, optional = true }
sha2 = { workspace = true, optional = true }
edgezero-store-aws = { workspace = true, optional = true }
edgezero-store-celld = { workspace = true, optional = true }

Expand Down
51 changes: 51 additions & 0 deletions crates/edgezero-adapter-axum/src/dev_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@ where
let metadata = A::stores();
let sources = NativeSourcePlan::validate(metadata, &options, overrides)?;
init_logging::<A>(options.logging_level())?;
native.geo.log_snapshot();
let mut app = build_app_for_dispatch::<A>()?;
let stores = build_production_stores(metadata, &options, sources).await?;
let transport = AxumOutboundClient::try_transport()
Expand Down Expand Up @@ -3481,6 +3482,56 @@ mod native_store_tests {
drop(listener);
}

#[cfg(feature = "geo-maxmind")]
#[test]
fn geo_maxmind_prepared_before_ready_stop_skips_initializer_and_bind() {
use crate::geo::{MaxMindGeoConfig, NativeGeoSelection, ReaderIpExpectation};
let options = options().with_geo(NativeGeoSelection::MaxMind {
config: MaxMindGeoConfig::new(
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/geoip/city.mmdb"),
ReaderIpExpectation::PeerAllowed,
),
});
let address = options.addr();
let initialized = Rc::new(Cell::new(false));
let observed = Rc::clone(&initialized);
run_app_with_options_and_initializer::<CustomApp, _, _>(
options,
future::ready(()),
move |_app, _stores| {
Box::pin(async move {
observed.set(true);
Ok(())
})
},
)
.unwrap();
assert!(!initialized.get());
drop(StdTcpListener::bind(address).expect("ready stop never binds"));
}

#[cfg(feature = "geo-maxmind")]
#[tokio::test(flavor = "current_thread")]
async fn geo_maxmind_invalid_database_fails_before_runner_resources() {
use crate::geo::{MaxMindGeoConfig, NativeGeoSelection, ReaderIpExpectation};
let root = tempfile::tempdir().unwrap();
let path = root.path().join("PRIVATE_DATABASE_PATH");
fs::write(&path, b"not an MMDB").unwrap();
let options = options().with_geo(NativeGeoSelection::MaxMind {
config: MaxMindGeoConfig::new(path, ReaderIpExpectation::PeerAllowed),
});
let address = options.addr();
let error = run_app_with_options_and_initializer::<CustomApp, _, _>(
options,
future::pending::<()>(),
|_app, _stores| Box::pin(async { panic!("initializer must not run") }),
)
.unwrap_err();
assert_eq!(error.to_string(), "native_geo: invalid database");
assert!(!format!("{error:?}").contains("PRIVATE_DATABASE_PATH"));
drop(StdTcpListener::bind(address).expect("invalid database never binds"));
}

#[test]
fn ready_and_rc_deferred_inputs_share_completed_registries_with_requests() {
let options = options();
Expand Down
Loading
Loading