Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eadac99e20 | |||
| 9ecbdfc3af | |||
| 59aeb63272 | |||
| d7a7bbe525 | |||
| c17a205ada | |||
| 8383a35352 | |||
| 50bc3babb7 | |||
| 46268edf9c | |||
| f2091cc9d6 | |||
| 0b58b6f728 | |||
| e709e30e43 | |||
| 29b405f813 | |||
| f5d22a66f6 | |||
| bf7cd15428 | |||
| 757da6a456 | |||
| 62c581a9ae | |||
| 32e06e19e7 | |||
| fd1b524037 | |||
| 36d0adfe92 | |||
| fcf782674c | |||
| 4b35c36299 | |||
| ca7cbac320 | |||
| e410aecf40 | |||
| ae76335c31 | |||
| dac7f1f83c | |||
| 31ff3645c5 | |||
| 6dd3b78a74 | |||
| 0ab14f7041 | |||
| 9c6310264e | |||
| aa37bfb7ff | |||
| 66c2454775 | |||
| 670f383faa | |||
| 9714351fd8 | |||
| 7352499328 | |||
| 06717037e5 | |||
| 1a7aa2ce90 | |||
| c594361cd1 | |||
| df52f10f52 | |||
| 6a96d8205b | |||
| 22793bc45e | |||
| 6eb8f29235 | |||
| c9ef46c51d | |||
| f1024bc976 | |||
| 1aec4c2f8e | |||
| 1b79107726 | |||
| a69b473ba1 | |||
| fe01c922c0 | |||
| 4b0fbc663a | |||
| 4b292ca142 | |||
| 9262e24892 | |||
| e0c74c5eb0 | |||
| a293d6da7d | |||
| f12a554e85 | |||
| 8c3a797750 | |||
| 00d0ae0b5b | |||
| de0ae687ef | |||
| 42c051dfa3 | |||
| 26f4dd8f39 | |||
| 2d37c33a3d | |||
| f1be6ae788 | |||
| c13b4aa745 | |||
| 68eae18b8b | |||
| 28dc7cae4d | |||
| 37f3ef58a3 | |||
| 52f98de73b | |||
| 6d63ba1f4c | |||
| 6170ca2a14 | |||
| 2cc59aadc5 | |||
| 89dc865ec6 | |||
| 6d1d9d58a5 | |||
| 3057721845 | |||
| 54c7a01482 | |||
| 76993a9b94 | |||
| b00e1f2fff | |||
| ef7974fde9 | |||
| a488a1b489 | |||
| bb5b43492a | |||
| f9e2311574 | |||
| 50d768976f | |||
| 3825d5f173 | |||
| 9b076197b1 | |||
| 7b96adf7a8 | |||
| 18d271f481 | |||
| 752c7915b3 | |||
| 6c01c9fceb | |||
| 4e7b4715b0 | |||
| 4fdbcb051a | |||
| 47c6006bb7 | |||
| dcfd0f77ad | |||
| 78fb779010 | |||
| b4544c2b48 | |||
| 90e07d9980 | |||
| b8479e1cde | |||
| 37e3a101b1 | |||
| 45a1074377 | |||
| 1b9af19e20 | |||
| 7a339d4c4d | |||
| 568268d39b | |||
| 9c5847dc67 | |||
| 22db132c09 | |||
| 5496cce5c9 | |||
| 8de37eb6c9 | |||
| 40a7a87c61 | |||
| 95ee3a7c7d | |||
| 4736f1eb52 | |||
| c23e139eaa | |||
| bfcb4c79bb | |||
| 2933732225 | |||
| 2e059865a1 | |||
| 06c0c36c67 | |||
| bdc0f5022d | |||
| b742ace7e0 | |||
| 82a9563ca0 | |||
| ef25480c20 | |||
| bcce854a8b | |||
| f960bfa91b | |||
| 96e3ff2af9 | |||
| 4f991061dd | |||
| e5aef76256 | |||
| 6acc54d2bc | |||
| ab6e08dd13 | |||
| e09066858c | |||
| 842ce93a60 | |||
| ce26105986 | |||
| cc95358385 | |||
| cc04a09ed7 | |||
| ae47d53f0c | |||
| e0ff09f323 | |||
| 10707fd2c5 | |||
| 9bdd2af14c | |||
| 228ef8b158 | |||
| d820131d2c | |||
| 054715a600 | |||
| 3f560180b7 | |||
| f62dbbdae0 | |||
| edecc4ba01 | |||
| 1a4d64a0e5 | |||
| 58c0e289c2 | |||
| 6d8edc4bc7 | |||
| a44cdf1c7c | |||
| 6b8a6283a4 | |||
| 94151965bb | |||
| e8ca490db1 | |||
| fe7470ea44 | |||
| 21d52244cb | |||
| c0c58026a8 | |||
| 0fe863c889 | |||
| 4e5d88f64c | |||
| 1559f6a912 | |||
| 766024be27 | |||
| 5ba181b118 | |||
| 76fc9f4a90 | |||
| 8ca6af7c86 | |||
| 45e14a7fb1 | |||
| a38917cb9b | |||
| cf8a399089 | |||
| ba01820586 | |||
| 8c799b2976 | |||
| de4fb6291c | |||
| 81fd37e5c0 | |||
| 219f3af967 | |||
| aea7442525 | |||
| 1525aed657 | |||
| 943b5fa8bc | |||
| 71e0c025c6 | |||
| 239c6c701b | |||
| 91d0b7bdad | |||
| 99b28b2b6f | |||
| 5627ada57e | |||
| 4e1228fff0 | |||
| 04be5624fa | |||
| a6fe1b1de7 | |||
| c5971d0e9d | |||
| 06dd74ba34 | |||
| c6a0256b03 | |||
| d04b61a88b | |||
| 70a119ac58 | |||
| e2fe3a60a6 | |||
| 71301ee0cc | |||
| aba6c9d4ac | |||
| c617bbb240 | |||
| 1f8144eb11 | |||
| 694135c81b | |||
| e815f08505 | |||
| f402da8e60 | |||
| 34a500d0a2 | |||
| ef52f25564 | |||
| 010b013094 | |||
| c503a5f0e8 | |||
| 781afd3522 | |||
| 58083df0b0 | |||
| 4e8d29d0c5 | |||
| 66797efa80 | |||
| 5a26fa262e | |||
| 73a34935ae | |||
| a8086675d9 | |||
| 0453345d65 | |||
| b56d9505e6 | |||
| bdacc72003 | |||
| 9eca9efd82 | |||
| cc74d218fc | |||
| dea8a287e6 | |||
| fc5d310935 | |||
| de27778192 | |||
| 37c4a3aa36 |
@@ -0,0 +1,36 @@
|
||||
# External Contributor Acceptance Checklist
|
||||
|
||||
This document outlines the requirements that all external contributors must complete before submitting a pull request to the Nym repository. Following this checklist ensures code quality, maintainability, and alignment with project standards.
|
||||
|
||||
## Code Quality and Compilation
|
||||
|
||||
All contributions must compile successfully without warnings or errors across the supported platforms. Before submitting your pull request, verify that your code compiles cleanly using `cargo build --workspace --all-targets`. The codebase uses the latest Rust version in most placesas specified in the workspace Cargo.toml, and your contribution must be compatible with this requirement. All code must pass `cargo clippy --workspace --all-targets -- -D warnings` without any clippy warnings or lints. This means zero tolerance for clippy suggestions, unused imports, dead code, or any other code quality issues that clippy would flag. Additionally, all code must be properly formatted using `cargo fmt --all`, and the CI pipeline will reject any pull request that fails the formatting check.
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
Every contribution must include appropriate test coverage for the functionality being added or modified. For new features, include unit tests that verify the core behavior and edge cases. For bug fixes, include regression tests that prevent the issue from reoccurring. All existing tests must continue to pass, and you must run `cargo test --workspace` locally to verify this before submission. Integration tests should be included when the contribution affects interactions between multiple components or systems. Test coverage should demonstrate that the code behaves correctly under both normal and error conditions, and any error handling paths should be explicitly tested.
|
||||
|
||||
## Documentation and Evidence
|
||||
|
||||
For user-facing features or significant changes, provide clear documentation of the functionality. This includes code comments explaining complex logic, updated README files if the contribution affects setup or usage instructions, and inline documentation for public APIs. When the contribution involves UI changes, user flows, or visual modifications, include screenshots or screen recordings that demonstrate the feature working as intended. These visual artifacts should show the complete user journey, including any error states or edge cases that users might encounter. For backend or infrastructure changes, provide diagrams or written descriptions of the architecture changes and how they integrate with existing systems.
|
||||
|
||||
## Component Impact Analysis
|
||||
|
||||
Clearly document which components, modules, or subsystems your contribution touches. This includes listing all files modified, added, or removed, and explaining the scope of changes within each file. Describe how your changes interact with existing code, including any dependencies you've added or modified, and explain why these dependencies are necessary. If your contribution affects multiple areas of the codebase, provide a clear explanation of how these changes work together and why they were implemented as a cohesive unit rather than separate contributions.
|
||||
|
||||
## Functional Benefits and Justification
|
||||
|
||||
Articulate the specific benefits your contribution provides to the project. Explain what problem it solves, who benefits from the change, and how it improves the user experience or system functionality. If the contribution addresses a specific issue or feature request, reference the relevant issue number and explain how your implementation fulfills the requirements. For performance improvements, include benchmarks or metrics that demonstrate the enhancement. For new features, explain the use case and how it fits into the broader project goals. If the contribution refactors existing code, explain why the refactoring was necessary and what improvements it brings in terms of maintainability, readability, or performance.
|
||||
|
||||
## Workflow Compliance
|
||||
|
||||
Your contribution must align with all project workflows and CI/CD requirements. This means your code must pass all automated checks in the GitHub Actions workflows, including build verification, linting, formatting, and test execution. The CI pipeline runs `cargo clippy` with `-D warnings` on all platforms, and any warnings will cause the build to fail. Ensure that your local development environment matches the CI environment as closely as possible, and run the same commands locally that the CI pipeline executes. If your contribution affects areas covered by specialized workflows such as contract compilation, WASM builds, or platform-specific builds, verify that those workflows also pass successfully.
|
||||
|
||||
## Code Review Readiness
|
||||
|
||||
Before marking your pull request as ready for review, ensure that all items in this checklist are complete. Your pull request description should reference this checklist and confirm that each requirement has been met. Include links to any external documentation, screenshots, or test results that support your contribution. Be prepared to answer questions about design decisions, implementation choices, and alternative approaches that were considered. The code should be self-explanatory where possible, with clear variable names, function names, and structure that make the intent obvious to reviewers who may not be familiar with the specific area of the codebase you've modified.
|
||||
|
||||
## Acceptance Criteria Summary
|
||||
|
||||
In summary, your contribution is ready for review when it compiles without warnings, passes all clippy checks with zero tolerance for warnings, includes comprehensive tests, provides clear documentation and visual evidence where applicable, clearly explains component impacts and functional benefits, complies with all workflow requirements, and is presented in a manner that facilitates efficient code review. Meeting these standards ensures that external contributions maintain the high quality bar expected in the Nym codebase and reduces the review burden on maintainers while preventing issues from reaching production.
|
||||
|
||||
@@ -37,6 +37,18 @@ jobs:
|
||||
- name: Install wasm-bindgen-cli
|
||||
run: cargo install wasm-bindgen-cli
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path nym-credential-proxy/vpn-api-lib-wasm/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
- name: "Build"
|
||||
run: make
|
||||
working-directory: nym-credential-proxy/vpn-api-lib-wasm
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -10,13 +10,13 @@ env:
|
||||
|
||||
jobs:
|
||||
check-if-tag-exists:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: SonarQube Scan
|
||||
uses: SonarSource/sonarqube-scan-action@v5
|
||||
uses: SonarSource/sonarqube-scan-action@v6
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: './ppa'
|
||||
|
||||
@@ -8,6 +8,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/first-interaction@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Thank you for raising this issue'
|
||||
pr-message: 'Thank you for making this first PR'
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue_message: 'Thank you for raising this issue'
|
||||
pr_message: 'Thank you for making this first PR'
|
||||
|
||||
@@ -27,3 +27,16 @@ jobs:
|
||||
steps:
|
||||
- if: github.event.pull_request.milestone == null && contains( env.LABELS, 'no-milestone' ) == false
|
||||
run: exit 1
|
||||
|
||||
check-contributor-checklist:
|
||||
name: Check Contributor Checklist Reference
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check PR description references checklist
|
||||
run: |
|
||||
PR_BODY="${{ github.event.pull_request.body }}"
|
||||
if [[ -z "$PR_BODY" ]] || [[ ! "$PR_BODY" =~ "CONTRIBUTOR_ACCEPTANCE_CHECKLIST" ]] && [[ ! "$PR_BODY" =~ "contributor.*checklist" ]]; then
|
||||
echo "::warning::PR description should reference .github/CONTRIBUTOR_ACCEPTANCE_CHECKLIST.md for external contributors"
|
||||
echo "Please ensure you have reviewed and completed all items in the contributor acceptance checklist."
|
||||
fi
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
@@ -8,7 +8,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -63,3 +63,5 @@ nym-api/redocly/formatted-openapi.json
|
||||
|
||||
**/settings.sql
|
||||
**/enter_db.sh
|
||||
|
||||
*.profraw
|
||||
@@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.21-mozzarella] (2025-11-25)
|
||||
|
||||
- [bugfix] Tunnel not waiting on MixnetClient to shut down cleanly ([#6225])
|
||||
- bugfix: fix credential proxy upgrade mode attestation url arg ([#6202])
|
||||
- HTTP API resilience enable & domain rotation conditions ([#6200])
|
||||
- Remove debug feature from http-macro spec in gateway probe ([#6195])
|
||||
- DNS relibility and troubleshooting ([#6179])
|
||||
- [bugfix] Distinguish authenticator errors by credential spent ([#6176])
|
||||
- Typescript SDK 1.4.1 ([#6146])
|
||||
- Enable URL rotation and retries for mixnet gateway init ([#6126])
|
||||
- Feature/credential proxy jwt ([#5957])
|
||||
|
||||
[#6225]: https://github.com/nymtech/nym/pull/6225
|
||||
[#6202]: https://github.com/nymtech/nym/pull/6202
|
||||
[#6200]: https://github.com/nymtech/nym/pull/6200
|
||||
[#6195]: https://github.com/nymtech/nym/pull/6195
|
||||
[#6179]: https://github.com/nymtech/nym/pull/6179
|
||||
[#6176]: https://github.com/nymtech/nym/pull/6176
|
||||
[#6146]: https://github.com/nymtech/nym/pull/6146
|
||||
[#6126]: https://github.com/nymtech/nym/pull/6126
|
||||
[#5957]: https://github.com/nymtech/nym/pull/5957
|
||||
|
||||
## [2025.20-leerdammer] (2025-11-12)
|
||||
|
||||
- Max/tweak ts sdk actions ([#6185])
|
||||
|
||||
Generated
+522
-400
File diff suppressed because it is too large
Load Diff
+11
-5
@@ -87,7 +87,9 @@ members = [
|
||||
"common/nymsphinx/params",
|
||||
"common/nymsphinx/routing",
|
||||
"common/nymsphinx/types",
|
||||
"common/nyxd-scraper",
|
||||
"common/nyxd-scraper-sqlite",
|
||||
"common/nyxd-scraper-psql",
|
||||
"common/nyxd-scraper-shared",
|
||||
"common/pemstore",
|
||||
"common/registration",
|
||||
"common/serde-helpers",
|
||||
@@ -124,6 +126,7 @@ members = [
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-data-observatory",
|
||||
"nym-ip-packet-client",
|
||||
"nym-network-monitor",
|
||||
"nym-node",
|
||||
@@ -263,9 +266,10 @@ futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
glob = "0.3"
|
||||
handlebars = "3.5.5"
|
||||
hex = "0.4.3"
|
||||
hickory-resolver = "0.25"
|
||||
hickory-resolver = "0.25.2"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
http = "1"
|
||||
@@ -347,11 +351,11 @@ tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.15"
|
||||
toml = "0.8.22"
|
||||
tower = "0.5.2"
|
||||
tower-http = "0.5.2"
|
||||
tower-http = "0.6.6"
|
||||
tracing = "0.1.41"
|
||||
tracing-log = "0.2"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-subscriber = "0.3.20"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-indicatif = "0.3.9"
|
||||
tracing-test = "0.2.5"
|
||||
@@ -398,7 +402,9 @@ cw-multi-test = "=2.3.2"
|
||||
bip32 = { version = "0.5.3", default-features = false }
|
||||
|
||||
|
||||
cosmrs = { version = "0.21.1" }
|
||||
cosmrs = { version = "0.22.0" }
|
||||
cosmos-sdk-proto = { version = "0.27.0" }
|
||||
ibc-proto = { version = "0.52.0" }
|
||||
tendermint = "0.40.4"
|
||||
tendermint-rpc = "0.40.4"
|
||||
prost = { version = "0.13", default-features = false }
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# config file for ansible -- http://ansible.com/
|
||||
# ==============================================
|
||||
|
||||
# nearly all parameters can be overridden in ansible-playbook
|
||||
# or with command line flags. ansible will read ANSIBLE_CONFIG,
|
||||
# ansible.cfg in the current working directory, .ansible.cfg in
|
||||
# the home directory or /etc/ansible/ansible.cfg, whichever it
|
||||
# finds first
|
||||
|
||||
[defaults]
|
||||
# some basic default values...
|
||||
|
||||
inventory = inventory/all
|
||||
#library = /usr/share/my_modules/
|
||||
remote_tmp = $HOME/.ansible/tmp
|
||||
pattern = *
|
||||
forks = 5
|
||||
poll_interval = 15
|
||||
transport = smart
|
||||
remote_port = 22
|
||||
module_lang = C
|
||||
|
||||
# plays will gather facts by default, which contain information about
|
||||
# the remote system.
|
||||
#
|
||||
# smart - gather by default, but don't regather if already gathered
|
||||
# implicit - gather by default, turn off with gather_facts: False
|
||||
# explicit - do not gather by default, must say gather_facts: True
|
||||
gathering = implicit
|
||||
|
||||
# additional paths to search for roles in, colon separated
|
||||
roles_path = ../roles
|
||||
|
||||
# uncomment this to disable SSH key host checking
|
||||
host_key_checking = False
|
||||
|
||||
# what flags to pass to sudo
|
||||
#sudo_flags = -H
|
||||
|
||||
# SSH timeout
|
||||
timeout = 100
|
||||
|
||||
# default user to use for playbooks if user is not specified
|
||||
# (/usr/bin/ansible will use current user as default)
|
||||
#remote_user = root
|
||||
|
||||
# logging is off by default unless this path is defined
|
||||
# if so defined, consider logrotate
|
||||
#log_path = /var/log/ansible.log
|
||||
|
||||
# default module name for /usr/bin/ansible
|
||||
#module_name = command
|
||||
|
||||
# use this shell for commands executed under sudo
|
||||
# you may need to change this to bin/bash in rare instances
|
||||
# if sudo is constrained
|
||||
#executable = /bin/sh
|
||||
|
||||
# if inventory variables overlap, does the higher precedence one win
|
||||
# or are hash values merged together? The default is 'replace' but
|
||||
# this can also be set to 'merge'.
|
||||
#hash_behaviour = replace
|
||||
|
||||
# list any Jinja2 extensions to enable here:
|
||||
#jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n
|
||||
|
||||
# if set, always use this private key file for authentication, same as
|
||||
# if passing --private -key to ansible or ansible-playbook
|
||||
#private_key_file = /path/to/file
|
||||
|
||||
# format of string {{ ansible_managed }} available within Jinja2
|
||||
# templates indicates to users editing templates files will be replaced.
|
||||
# replacing {file}, {host} and {uid} and strftime codes with proper values.
|
||||
ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}
|
||||
|
||||
# by default, ansible-playbook will display "Skipping [host]" if it determines a task
|
||||
# should not be run on a host. Set this to "False" if you don't want to see these "Skipping"
|
||||
# messages. NOTE: the task header will still be shown regardless of whether or not the
|
||||
# task is skipped.
|
||||
#display_skipped_hosts = True
|
||||
|
||||
# by default (as of 1.3), Ansible will raise errors when attempting to dereference
|
||||
# Jinja2 variables that are not set in templates or action lines. Uncomment this line
|
||||
# to revert the behavior to pre-1.3.
|
||||
#error_on_undefined_vars = False
|
||||
|
||||
# by default (as of 1.6), Ansible may display warnings based on the configuration of the
|
||||
# system running ansible itself. This may include warnings about 3rd party packages or
|
||||
# other conditions that should be resolved if possible.
|
||||
# to disable these warnings, set the following value to False:
|
||||
#system_warnings = True
|
||||
|
||||
# by default (as of 1.4), Ansible may display deprecation warnings for language
|
||||
# features that should no longer be used and will be removed in future versions.
|
||||
# to disable these warnings, set the following value to False:
|
||||
#deprecation_warnings = True
|
||||
|
||||
# (as of 1.8), Ansible can optionally warn when usage of the shell and
|
||||
# command module appear to be simplified by using a default Ansible module
|
||||
# instead. These warnings can be silenced by adjusting the following
|
||||
# setting or adding warn=yes or warn=no to the end of the command line
|
||||
# parameter string. This will for example suggest using the git module
|
||||
# instead of shelling out to the git command.
|
||||
# command_warnings = False
|
||||
|
||||
|
||||
# set plugin path directories here, separate with colons
|
||||
action_plugins = ../../other/plugins/action
|
||||
callback_plugins = ../../other/plugins/callback
|
||||
connection_plugins = ../../other/plugins/connection
|
||||
lookup_plugins = ../../other/plugins/lookup
|
||||
vars_plugins = ../../other/plugins/vars
|
||||
filter_plugins = ../../other/plugins/filter
|
||||
|
||||
# by default callbacks are not loaded for /bin/ansible, enable this if you
|
||||
# want, for example, a notification or logging callback to also apply to
|
||||
# /bin/ansible runs
|
||||
#bin_ansible_callbacks = False
|
||||
|
||||
|
||||
# don't like cows? that's unfortunate.
|
||||
# set to 1 if you don't want cowsay support or export ANSIBLE_NOCOWS=1
|
||||
#nocows = 1
|
||||
|
||||
# don't like colors either?
|
||||
# set to 1 if you don't want colors, or export ANSIBLE_NOCOLOR=1
|
||||
#nocolor = 1
|
||||
|
||||
# the CA certificate path used for validating SSL certs. This path
|
||||
# should exist on the controlling node, not the target nodes
|
||||
# common locations:
|
||||
# RHEL/CentOS: /etc/pki/tls/certs/ca-bundle.crt
|
||||
# Fedora : /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
|
||||
# Ubuntu : /usr/share/ca-certificates/cacert.org/cacert.org.crt
|
||||
#ca_file_path =
|
||||
|
||||
# the http user-agent string to use when fetching urls. Some web server
|
||||
# operators block the default urllib user agent as it is frequently used
|
||||
# by malicious attacks/scripts, so we set it to something unique to
|
||||
# avoid issues.
|
||||
#http_user_agent = ansible-agent
|
||||
|
||||
# if set to a persistant type (not 'memory', for example 'redis') fact values
|
||||
# from previous runs in Ansible will be stored. This may be useful when
|
||||
# wanting to use, for example, IP information from one group of servers
|
||||
# without having to talk to them in the same playbook run to get their
|
||||
# current IP information.
|
||||
fact_caching = memory
|
||||
|
||||
[paramiko_connection]
|
||||
|
||||
# uncomment this line to cause the paramiko connection plugin to not record new host
|
||||
# keys encountered. Increases performance on new host additions. Setting works independently of the
|
||||
# host key checking setting above.
|
||||
#record_host_keys=False
|
||||
|
||||
# by default, Ansible requests a pseudo-terminal for commands executed under sudo. Uncomment this
|
||||
# line to disable this behaviour.
|
||||
#pty=False
|
||||
|
||||
[ssh_connection]
|
||||
|
||||
# ssh arguments to use
|
||||
# Leaving off ControlPersist will result in poor performance, so use
|
||||
# paramiko on older platforms rather than removing it
|
||||
#ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||
|
||||
# The path to use for the ControlPath sockets. This defaults to
|
||||
# "%(directory)s/ansible-ssh-%%h-%%p-%%r", however on some systems with
|
||||
# very long hostnames or very long path names (caused by long user names or
|
||||
# deeply nested home directories) this can exceed the character limit on
|
||||
# file socket names (108 characters for most platforms). In that case, you
|
||||
# may wish to shorten the string below.
|
||||
#
|
||||
# Example:
|
||||
# control_path = %(directory)s/%%h-%%r
|
||||
#control_path = %(directory)s/ansible-ssh-%%h-%%p-%%r
|
||||
|
||||
# Enabling pipelining reduces the number of SSH operations required to
|
||||
# execute a module on the remote server. This can result in a significant
|
||||
# performance improvement when enabled, however when using "sudo:" you must
|
||||
# first disable 'requiretty' in /etc/sudoers
|
||||
#
|
||||
# By default, this option is disabled to preserve compatibility with
|
||||
# sudoers configurations that have requiretty (the default on many distros).
|
||||
#
|
||||
#pipelining = False
|
||||
|
||||
# if True, make ansible use scp if the connection type is ssh
|
||||
# (default is sftp)
|
||||
scp_if_ssh = True
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Nym node bonding / post-installation
|
||||
hosts: all # or a specific host/group
|
||||
gather_facts: false
|
||||
serial: 1
|
||||
|
||||
roles:
|
||||
- role: postinstall
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: "Deploy Nym node"
|
||||
hosts: all # or a specific host/group
|
||||
become: true
|
||||
roles:
|
||||
- base
|
||||
- nym
|
||||
- nginx
|
||||
- tunnel # comment out for mixnode
|
||||
- quic # comment out for mixnode or non-wireguard gateway
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
ansible_ssh_private_key_file: ~/.ssh/<SSH_KEY>
|
||||
|
||||
# nym_version: "v2025.21-mozzarella"
|
||||
#
|
||||
# NOTE:
|
||||
# if you want to pin Nym to a specific version instead of using the
|
||||
# latest release from GitHub in /tasks/main.yml then
|
||||
# uncomment the line above and set the tag
|
||||
|
||||
cli_url: "https://github.com/nymtech/nym/releases/download/nym-binaries-{{ nym_version }}/nym-cli"
|
||||
tunnel_manager_url: "https://github.com/nymtech/nym/raw/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh"
|
||||
quic_bridge_deployment_url: "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh"
|
||||
|
||||
# NOTE: These values will be used globally unless overwritten per node in inventory/all
|
||||
ansible_user: root # used for ssh, like `ssh root@nym-exit.ch-1.mynodes.net`
|
||||
email: "<EMAIL>" # used in certbot, description.toml and landing page
|
||||
website: "<WEBSITE>" # it is used in the description.toml
|
||||
description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
|
||||
|
||||
# NOTE: Set these vars if you want them globally for all nodes
|
||||
# Per node changes in inventory/all will overwrite these global ones:
|
||||
hostname: "" # this is a fallback, keep it and setup hostname per node in inventory/all
|
||||
# moniker: "<MONIKER>" # if not setup here not in inventory/all it get's derived from the hostname
|
||||
# mode: <MODE> # entry-gateway/exit-gateway/mixnode
|
||||
# wireguard_enabled: <WIREGUARD_ENABLED> # true/false
|
||||
|
||||
# NOTE: Possible vars to incule on landing page, etc.
|
||||
# operator_name: "<OPERATOR_NAME>"
|
||||
|
||||
packages:
|
||||
- tmux
|
||||
- speedtest-cli
|
||||
- nano
|
||||
- htop
|
||||
- git
|
||||
- zip
|
||||
- nala
|
||||
- curl
|
||||
- neovim
|
||||
- ca-certificates
|
||||
- jq
|
||||
- wget
|
||||
- ufw
|
||||
@@ -0,0 +1,34 @@
|
||||
[nym_nodes]
|
||||
# READ CONFIGURATION GUIDE:
|
||||
# https://nym.com//docs/operators/orchestration/ansible#configuration
|
||||
|
||||
# VARIABLES INFO
|
||||
# required vars to set values per node:
|
||||
# `ansible_host`, `hostname`, `location`
|
||||
|
||||
# global vars can be set in the group_vars/all.yml, for example:
|
||||
# `email`, `ansible_user`, `moniker`, `description`, `mode`, `wireguard_enabled`
|
||||
# othersise they must be set per node!
|
||||
|
||||
############
|
||||
# TEMPLATE #
|
||||
############
|
||||
# node1 ansible_host=<YOUR_SERVER_IP> ansible_user=<USER> hostname=<HOSTNAME> location=<LOCATION> email=<EMAIL> mode=<MODE> wireguard_enabled=<true/false> moniker=<MONIKER> description=<DESCRIPTION>
|
||||
|
||||
# remove all comments and exchange the <VARIABLES> with your real values for each node
|
||||
# without <> brackets
|
||||
|
||||
# PRIORITY ORDER
|
||||
# anything setup globaly can be overwritten in this file per node
|
||||
# if provided here, it takes priority over the global setting
|
||||
|
||||
# EXAMPLES
|
||||
# exit + wireguard gateway:
|
||||
# node2 ansible_host=11.12.13.14 hostname=nym-exit.ch-1.mydomain.net mode=exit-gateway location=CH wireguard_enabled=true
|
||||
|
||||
# entry gateway, no wireguard:
|
||||
# node3 ansible_host=12.13.14.15 hostname=nym-entry.ch-2.mydomain.net mode=entry-gateway location=CH wireguard_enabled=false
|
||||
|
||||
# NOTE:
|
||||
# all examples above don't have defined user, email nor description as we use the definition from group_vars/main.yml without an attempt of overwriting it
|
||||
# all examples above don't have moniker defined as there is a function in /templates/description.toml.j2 deriving it from the hostname
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: "Upgrade Nym node"
|
||||
hosts: all # or a specific host/group or limit via -l on CLI (ansible-playbook playbooks/upgrade.yml -l mynode1)
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
roles:
|
||||
- base
|
||||
- upgrade
|
||||
@@ -0,0 +1,22 @@
|
||||
- name: Set hostname
|
||||
hostname:
|
||||
name: "{{ hostname }}"
|
||||
when: hostname is defined and hostname | length > 0
|
||||
|
||||
- name: Install aptitude
|
||||
apt:
|
||||
name: aptitude
|
||||
update_cache: yes
|
||||
state: present
|
||||
force_apt_get: yes
|
||||
|
||||
- name: Update packages
|
||||
apt:
|
||||
update_cache: yes
|
||||
upgrade: yes
|
||||
|
||||
- name: Install essential packages
|
||||
package:
|
||||
name: "{{ packages }}"
|
||||
state: latest
|
||||
update_cache: yes
|
||||
@@ -0,0 +1,61 @@
|
||||
- name: Install nginx and certbot
|
||||
apt:
|
||||
name:
|
||||
- nginx
|
||||
- certbot
|
||||
- python3-certbot-nginx
|
||||
state: present
|
||||
|
||||
- name: Create web root directory
|
||||
file:
|
||||
path: "/var/www/{{ hostname }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Create landing page template
|
||||
tags: landing
|
||||
template:
|
||||
src: landing.html.j2
|
||||
dest: "/var/www/{{ hostname }}/index.html"
|
||||
|
||||
- name: Remove default nginx site
|
||||
file:
|
||||
path: /etc/nginx/sites-enabled/default
|
||||
state: absent
|
||||
|
||||
- name: Add bare-bones nginx template
|
||||
template:
|
||||
src: nginx-site.conf.j2
|
||||
dest: "/etc/nginx/sites-available/{{ hostname }}"
|
||||
|
||||
- name: Enable nginx config
|
||||
file:
|
||||
src: "/etc/nginx/sites-available/{{ hostname }}"
|
||||
dest: "/etc/nginx/sites-enabled/{{ hostname }}"
|
||||
state: link
|
||||
|
||||
- name: Validate nginx configuration
|
||||
command: nginx -t
|
||||
changed_when: false
|
||||
|
||||
- name: Obtain SSL certificate
|
||||
command:
|
||||
cmd: "certbot --nginx --non-interactive --agree-tos --redirect -m {{ email }} -d {{ hostname }}"
|
||||
|
||||
- name: Add wss config from nginx template
|
||||
template:
|
||||
src: wss-config.conf.j2
|
||||
dest: "/etc/nginx/sites-available/nym-wss-config"
|
||||
|
||||
- name: Enable WSS config
|
||||
file:
|
||||
src: "/etc/nginx/sites-available/nym-wss-config"
|
||||
dest: "/etc/nginx/sites-enabled/nym-wss-config"
|
||||
state: link
|
||||
|
||||
- name: Validate nginx config after wss
|
||||
command: nginx -t
|
||||
changed_when: false
|
||||
|
||||
- name: Restart nginx to apply changes
|
||||
service: name=nginx state=restarted enabled=yes
|
||||
@@ -0,0 +1,218 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>This is a NYM Exit Gateway</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
/* font + theme tokens */
|
||||
font-family: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace;
|
||||
--background-color: #242B2D;
|
||||
--text-color: #FFFFFF;
|
||||
--link-color: #07ff94;
|
||||
--title-color: #07ff94;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
body {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 5vw;
|
||||
padding-right: 5vw;
|
||||
max-width: 1000px;
|
||||
color: var(--text-color); /* default text color */
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 55px;
|
||||
text-align: center;
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
p, a {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
filter: brightness(.8);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.links > a {
|
||||
margin: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>This is a NYM Exit Gateway</h1>
|
||||
|
||||
|
||||
<p>
|
||||
You are most likely accessing this website because you've had some issue with
|
||||
the traffic coming from this IP. This router is part of the <a
|
||||
href="https://nym.com/">NYM project</a>, which is
|
||||
dedicated to <a href="https://nym.com/about/mission">create</a> outstanding
|
||||
privacy software that is legally compliant without sacrificing integrity or
|
||||
having any backdoors.
|
||||
This router IP should be generating no other traffic, unless it has been
|
||||
compromised.
|
||||
</p>
|
||||
|
||||
<p><strong>
|
||||
If you are a representative of a company who feels that this router is being
|
||||
used to violate the DMCA, please be aware that this machine does not host or
|
||||
contain any illegal content. Also be aware that network infrastructure
|
||||
maintainers are not liable for the type of content that passes over their
|
||||
equipment, in accordance with <a
|
||||
href="https://www.law.cornell.edu/uscode/text/17/512">DMCA
|
||||
"safe harbor" provisions</a>. In other words, you will have just as much luck
|
||||
sending a takedown notice to the Internet backbone providers.
|
||||
</strong></p>
|
||||
|
||||
<p>
|
||||
Nym Network is operated by a decentralised community of node operators
|
||||
and stakers. Nym Network is trustless, meaning that no parts of the system
|
||||
nor its operators have access to information that might compromise the privacy
|
||||
of users. Nym software enacts a strict principle of data minimisation and has
|
||||
no back doors. The Nym mixnet works by encrypting packets in several layers
|
||||
and relaying those through a multi-layered network called a mixnet, eventually
|
||||
letting the traffic exit the Nym mixnet through an exit gateway like this one.
|
||||
This design makes it impossible for a service to know which user is connecting to it,
|
||||
since it can only see the IP-address of the Nym exit gateway:
|
||||
</p>
|
||||
|
||||
<p style="text-align:center;margin:40px 0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" viewBox="0 0 490.28 293.73" style="width:100%;max-width:600px" role="img" aria-label="Diagram of how a user connects through the Nym network">
|
||||
<desc>Illustration showing how a user might connect to a service through the Nym Network. The user first sends their data through three daisy-chained encrypted Nym nodes that exist on three different continents. Then the last Nym node in the chain connects to the target service over the normal internet.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.t {
|
||||
fill: var(--text-color);
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<!-- (SVG content left unchanged) -->
|
||||
<path fill="#6fc8b7" d="M257.89 69.4c-6.61-6.36-10.62-7.73-18.36-8.62-7.97-1.83-20.06-7.99-24.17-.67-3.29 5.85-18.2 12.3-16.87 2.08.92-7.03 11.06-13.28 17-17.37 8.69-5.99 24.97-2.87 26.1-10.28 1.04-6.86-8.33-13.22-8.55-2.3-.38 12.84-19.62 2.24-8.73-6.2 8.92-6.9 16.05-9.02 25.61-6.15 12.37 4.83 25.58-2.05 33.73-.71 12.37-2.01 24.69-5.25 37.39-3.96 13 .43 24.08-.14 37.06.63 9.8 1.58 16.5 2.87 26.37 3.6 6.6.48 17.68-.82 24.3 1.9 8.3 4.24.44 10.94-6.89 11.8-8.79 1.05-23.59-1.19-26.6 1.86-5.8 7.41 10.75 5.68 11.27 14.54.57 9.45-5.42 9.38-8.72 16-2.7 4.2.3 13.93-1.18 18.45-1.85 5.64-19.64 4.47-14.7 14.4 4.16 8.34 1.17 19.14-10.33 12.02-5.88-3.65-9.85-22.04-15.66-21.9-11.06.27-11.37 13.18-12.7 17.52-1.3 4.27-3.79 2.33-6-.63-3.54-4.76-7.75-14.22-12.01-17.32-6.12-4.46-10.75-1.17-15.55 2.83-5.63 4.69-8.78 7.82-7.46 16.5.78 9.1-12.9 15.84-14.98 24.09-2.61 10.32-2.57 22.12-8.81 31.47-4 5.98-14.03 20.12-21.27 14.97-7.5-5.34-7.22-14.6-9.56-23.08-2.5-9.02.6-17.35-2.57-26.2-2.45-6.82-6.23-14.54-13.01-13.24-6.5.92-15.08 1.38-19.23-2.97-5.65-5.93-6-10.1-6.61-18.56 1.65-6.94 5.79-12.64 10.38-18.63 3.4-4.42 17.45-10.39 25.26-7.83 10.35 3.38 17.43 10.5 28.95 8.57 3.12-.53 9.14-4.65 7.1-6.62zm-145.6 37.27c-4.96-1.27-11.57 1.13-11.8 6.94-1.48 5.59-4.82 10.62-5.8 16.32.56 6.42 4.34 12.02 8.18 16.97 3.72 3.85 8.58 7.37 9.3 13.1 1.24 5.88 1.6 11.92 2.28 17.87.34 9.37.95 19.67 7.29 27.16 4.26 3.83 8.4-2.15 6.52-6.3-.54-4.54-.6-9.11 1.01-13.27 4.2-6.7 7.32-10.57 12.44-16.64 5.6-7.16 12.74-11.75 14-20.9.56-4.26 5.72-13.86 1.7-16.72-3.14-2.3-15.83-4-18.86-6.49-2.36-1.71-3.86-9.2-9.86-12.07-4.91-3.1-10.28-6.73-16.4-5.97zm11.16-49.42c6.13-2.93 10.58-4.77 14.61-10.25 3.5-4.28 2.46-12.62-2.59-15.45-7.27-3.22-13.08 5.78-18.81 8.71-5.96 4.2-12.07-5.48-6.44-10.6 5.53-4.13.38-9.2-5.66-8.48-6.12.8-12.48-1.45-18.6-1.73-5.3-.7-10.13-1-15.45-1.37-5.37-.05-16.51-2.23-25.13.87-5.42 1.79-12.5 5.3-16.73 9.06-4.85 4.2.2 7.56 5.54 7.45 5.3-.22 16.8-5.36 20.16.98 3.68 8.13-5.82 18.29-5.2 26.69.1 6.2 3.37 11 4.74 16.98 1.62 5.94 6.17 10.45 10 15.14 4.7 5.06 13.06 6.3 19.53 8.23 7.46.14 3.34-9.23 3.01-14.11 1.77-7.15 8.49-7.82 12.68-13.5 7.14-7.72 16.41-13.4 24.34-18.62zM190.88 3.1c-4.69 0-13.33.04-18.17-.34-7.65.12-13.1-.62-19.48-1.09-3.67.39-9.09 3.34-5.28 7.04 3.8.94 7.32 4.92 7.1 9.31 1.32 4.68 1.2 11.96 6.53 13.88 4.76-.2 7.12-7.6 11.93-8.25 6.85-2.05 12.5-4.58 17.87-9.09 2.48-2.76 7.94-6.38 5.26-10.33-1.55-1.31-2.18-.64-5.76-1.13zm178.81 157.37c-2.66 10.08-5.88 24.97 9.4 15.43 7.97-5.72 12.58-2.02 17.47 1.15.5.43 2.65 9.2 7.19 8.53 5.43-2.1 11.55-5.1 14.96-11.2 2.6-4.62 3.6-12.39 2.76-13.22-3.18-3.43-6.24-11.03-7.7-15.1-.76-2.14-2.24-2.6-2.74-.4-2.82 12.85-6.04 1.22-10.12-.05-8.2-1.67-29.62 7.17-31.22 14.86z"/>
|
||||
<g fill="none">
|
||||
<path stroke="#cf63a6" stroke-linecap="round" stroke-width="2.76" d="M135.2 140.58c61.4-3.82 115.95-118.83 151.45-103.33"/>
|
||||
<path stroke="#cf63a6" stroke-linecap="round" stroke-width="2.76" d="M74.43 46.66c38.15 8.21 64.05 42.26 60.78 93.92M286.65 37.25c-9.6 39.44-3.57 57.12-35.64 91.98"/>
|
||||
<path stroke="#e4c101" stroke-dasharray="9.06,2.265" stroke-width="2.27" d="M397.92 162.52c-31.38 1.26-90.89-53.54-148.3-36.17"/>
|
||||
<path stroke="#cf63a6" stroke-linecap="round" stroke-width="2.77" d="M17.6 245.88c14.35 0 14.4.05 28-.03"/>
|
||||
<path stroke="#e3bf01" stroke-dasharray="9.06,2.265" stroke-width="2.27" d="M46.26 274.14c-17.52-.12-16.68.08-30.34.07"/>
|
||||
</g>
|
||||
<g transform="translate(120.8 -35.81)">
|
||||
<circle cx="509.78" cy="68.74" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="440.95" cy="251.87" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="212.62" cy="272.19" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="92.12" cy="87.56" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="730.88" cy="315.83" r="18.12" fill="#67727b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="-102.85" cy="282.18" r="9.18" fill="#240a3b"/>
|
||||
<circle cx="-102.85" cy="309.94" r="9.18" fill="#67727b"/>
|
||||
</g>
|
||||
<g class="t">
|
||||
<text xml:space="preserve" x="-24.76" y="10.37" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="-24.76" y="10.37">The user</tspan></text>
|
||||
<text xml:space="preserve" x="150.63" y="196.62" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="150.63" y="196.62">This server</tspan></text>
|
||||
<text xml:space="preserve" x="346.39" y="202.63" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="346.39" y="202.63">Your service</tspan></text>
|
||||
<text xml:space="preserve" x="34.52" y="249.07" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="34.52" y="249.07">Nym network link</tspan></text>
|
||||
<text xml:space="preserve" x="34.13" y="276.05" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="34.13" y="276.05">Unencrypted link</tspan></text>
|
||||
<path fill="none" stroke-linecap="round" stroke-width="1.67" d="M222.6 184.1c-2.6-15.27 8.95-23.6 18.43-38.86m186.75 45.61c-.68-10.17-9.4-17.68-18.08-23.49"/>
|
||||
<path fill="none" stroke-linecap="round" stroke-width="1.67" d="M240.99 153.41c.35-3.41 1.19-6.17.04-8.17m-7.15 5.48c1.83-2.8 4.58-4.45 7.15-5.48"/>
|
||||
<path fill="none" stroke-linecap="round" stroke-width="1.67" d="M412.43 173.21c-2.2-3.15-2.54-3.85-2.73-5.85m0 0c2.46-.65 3.85.01 6.67 1.24M61.62 40.8C48.89 36.98 36.45 27.54 36.9 18.96M61.62 40.8c.05-2.58-3.58-4.8-5.25-5.26m-2.65 6.04c1.8.54 6.8 1.31 7.9-.78"/>
|
||||
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.44" d="M1.22 229.4h247.74v63.1H1.22z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</p>
|
||||
|
||||
<p><a href="https://nym.com/about/mixnet">Read more about how Nym works.</a></p>
|
||||
|
||||
<p>
|
||||
Nym relies on a growing ecosystem of users, developers and researcher partners
|
||||
aligned with the mission to make sure Nym software is running, remains usable
|
||||
and solves real problems. While Nym is not designed for malicious computer
|
||||
users, it is true that they can use the network for malicious ends. This
|
||||
is largely because criminals and hackers have significantly better access to
|
||||
privacy and anonymity than do the regular users whom they prey upon. Criminals
|
||||
can and do build, sell, and trade far larger and more powerful networks than
|
||||
Nym on a daily basis. Thus, in the mind of this operator, the social need for
|
||||
easily accessible censorship-resistant private, anonymous communication trumps
|
||||
the risk of unskilled bad actors, who are almost always more easily uncovered
|
||||
by traditional police work than by extensive monitoring and surveillance anyway.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In terms of applicable law, the best way to understand Nym is to consider it a
|
||||
network of routers operating as common carriers, much like the Internet
|
||||
backbone. However, unlike the Internet backbone routers, Nym mixnodes do not
|
||||
contain identifiable routing information about the source of a packet and do
|
||||
mix the user internet traffic with that of other users, making communications
|
||||
private and protecting not just the user content but the metadata
|
||||
(user's IP address, who the user talks to, when, where, from what device and
|
||||
more) and no single Nym node can determine both the origin and destination
|
||||
of a given transmission.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As such, there is nothing the operator of this Exit Gateway can do to help you
|
||||
track the connection further. This Exit Gateway maintains no logs of any of the
|
||||
Nym Network, so there is little that can be done to trace either legitimate or
|
||||
illegitimate traffic and most importantly the operator cannot tell apart one from
|
||||
the other because of the cryptography design making such selection impossible
|
||||
for the operator. Attempts to seize this router will accomplish nothing.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To decentralise and enable privacy for a broad range of services, this
|
||||
Exit Gateway adopts an <a href="https://nymtech.net/.wellknown/network-requester/exit-policy.txt">Exit Policy</a>
|
||||
serving as a safeguard.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
That being said, if you still have a complaint about the router, you may email the
|
||||
<a href="mailto:{{ email }}">maintainer</a>. If complaints are related to a particular service that is being abused,
|
||||
the maintainer will submit that to the NYM Operators Community in order to add it to the Exit Policy cited above.
|
||||
The community governance can only blacklist entire IP:port destinations across the entire network.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You also have the option of blocking this IP address and others on the Nym network if you so desire.
|
||||
The Nym project provides a <a href="https://nym.com/explorer">
|
||||
web service</a> to fetch a list of all IP addresses of Nym Gateway Exit nodes that allow exiting to a
|
||||
specified IP:port combination. Please be considerate when using these options.
|
||||
</p>
|
||||
<p style="text-align:center">
|
||||
<img
|
||||
class="logo"
|
||||
src="https://raw.githubusercontent.com/nymtech/websites/main/www/nym.com/public/images/Nym_meta_Image.png"
|
||||
alt=""
|
||||
style="max-width:320px;width:100%;height:auto"
|
||||
onerror="this.onerror=null;this.src='/images/nym_logo.png';"
|
||||
/>
|
||||
</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name {{ hostname }};
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
server {
|
||||
listen 9001 ssl http2;
|
||||
listen [::]:9001 ssl http2;
|
||||
|
||||
server_name {{ hostname }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ hostname }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ hostname }}/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location /favicon.ico {
|
||||
return 204;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location / {
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
|
||||
add_header 'Access-Control-Allow-Headers' '*' always;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
proxy_pass http://localhost:9000;
|
||||
proxy_intercept_errors on;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
# Where binaries live
|
||||
nym_install_dir: /root/nym-binaries
|
||||
|
||||
# nym-node run arguments (defaults, can be overridden per host/group)
|
||||
http_bind_address: "0.0.0.0:8080" # maps to --http-bind-address
|
||||
mixnet_bind_address: "0.0.0.0:1789" # maps to --mixnet-bind-address
|
||||
|
||||
|
||||
# WireGuard boolean
|
||||
wireguard_enabled: "{{ wireguard_enabled | default(false) | bool }}"
|
||||
|
||||
# Landing page base dir, hostname is appended in the task
|
||||
landing_page_assets_base_dir: "/var/www"
|
||||
|
||||
# Flag toggles
|
||||
# accept_operator_terms: true # controls --accept-operator-terms-and-conditions
|
||||
nym_write_flag: true # controls -w
|
||||
nym_init_only_flag: true # controls --init-only
|
||||
wss_port: 9001 # controlls --announce-wss-port
|
||||
|
||||
# Optional: extra flags if you want to append more later
|
||||
nym_extra_flags: ""
|
||||
|
||||
# CLI URL (nym_version can be set elsewhere / via GitHub API)
|
||||
nym_cli_url: "https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-cli"
|
||||
|
||||
# UFW
|
||||
nym_ufw_enable: true
|
||||
|
||||
nym_ufw_rules:
|
||||
- { port: 22, proto: tcp }
|
||||
- { port: 80, proto: tcp }
|
||||
- { port: 443, proto: tcp }
|
||||
- { port: 1789, proto: tcp }
|
||||
- { port: 1790, proto: tcp }
|
||||
- { port: 8080, proto: tcp }
|
||||
- { port: 9000, proto: tcp }
|
||||
- { port: 9001, proto: tcp }
|
||||
- { port: 51822, proto: udp }
|
||||
@@ -0,0 +1 @@
|
||||
#!/bin/bash
|
||||
@@ -0,0 +1,3 @@
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
# Useful when the host is behind a NAT
|
||||
- name: Fetch the public IP address
|
||||
command: "curl -4 canhazip.com"
|
||||
register: ipv4
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Set public IP address
|
||||
set_fact:
|
||||
public_ip: "{{ ipv4.stdout | default(ansible_default_ipv4.address) }}"
|
||||
|
||||
- name: Initialize nym node
|
||||
# Delete the part from --hostname onward if you run mode=mixnode only
|
||||
command:
|
||||
cmd: >
|
||||
{{ nym_install_dir }}/nym-node run
|
||||
--mode {{ mode }}
|
||||
--public-ips {{ public_ip }}
|
||||
--http-bind-address {{ http_bind_address }}
|
||||
--mixnet-bind-address {{ mixnet_bind_address }}
|
||||
--location {{ location }}
|
||||
{% if accept_operator_terms %}--accept-operator-terms-and-conditions{% endif %}
|
||||
|
||||
{{ nym_extra_flags }}
|
||||
|
||||
--hostname {{ hostname }}
|
||||
--wireguard-enabled {{ wireguard_enabled }}
|
||||
--landing-page-assets-path {{ landing_page_assets_base_dir }}/{{ hostname }}/
|
||||
{% if nym_write_flag %}-w{% endif %}
|
||||
{% if nym_init_only_flag %}--init-only{% endif %}
|
||||
--announce-wss-port {{ wss_port }}
|
||||
|
||||
|
||||
- name: Update nym description
|
||||
template:
|
||||
src: description.toml.j2
|
||||
dest: /root/.nym/nym-nodes/default-nym-node/data/description.toml
|
||||
@@ -0,0 +1,25 @@
|
||||
- name: Configure UFW rules
|
||||
ufw:
|
||||
rule: allow
|
||||
port: "{{ item.port }}"
|
||||
proto: "{{ item.proto }}"
|
||||
comment: "{{ item.comment | default(omit) }}"
|
||||
loop: "{{ nym_ufw_rules }}"
|
||||
loop_control:
|
||||
label: "{{ item.port }}/{{ item.proto }}"
|
||||
when:
|
||||
- nym_ufw_enable
|
||||
- item.when | default(true)
|
||||
|
||||
- name: Allow bandwidth/topup rule inside WG tunnel
|
||||
command: >
|
||||
ufw allow in on nymwg to any port 51830 proto tcp comment 'bandwidth queries/topup'
|
||||
when:
|
||||
- nym_ufw_enable
|
||||
- (wireguard_enabled | bool)
|
||||
|
||||
- name: Enable UFW
|
||||
ufw:
|
||||
state: enabled
|
||||
when:
|
||||
nym_ufw_enable
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
- name: Create nym directory
|
||||
file:
|
||||
path: "{{ nym_install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Get latest Nym release metadata
|
||||
uri:
|
||||
url: https://api.github.com/repos/nymtech/nym/releases/latest
|
||||
return_content: yes
|
||||
register: latest_release
|
||||
when: nym_version is not defined or nym_version == 'latest'
|
||||
|
||||
- name: Set nym_version from GitHub API
|
||||
set_fact:
|
||||
nym_version: "{{ latest_release.json.tag_name }}"
|
||||
when: nym_version is not defined or nym_version == 'latest'
|
||||
|
||||
- name: Set binary URL
|
||||
set_fact:
|
||||
binary_url: "https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-node"
|
||||
|
||||
- name: Download nym-node binary
|
||||
get_url:
|
||||
url: "{{ binary_url }}"
|
||||
dest: "{{ nym_install_dir }}/nym-node"
|
||||
mode: "0755"
|
||||
|
||||
- name: Download nym-cli binary
|
||||
get_url:
|
||||
url: "{{ nym_cli_url }}"
|
||||
dest: "{{ nym_install_dir }}/nym-cli"
|
||||
mode: "0755"
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Install Nym binaries
|
||||
import_tasks: install.yml
|
||||
|
||||
- name: Configure Nym node
|
||||
import_tasks: config.yml
|
||||
|
||||
- name: Configure firewall for Nym
|
||||
import_tasks: firewall.yml
|
||||
|
||||
- name: Configure and start Nym service
|
||||
import_tasks: service.yml
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Template systemd service
|
||||
tags: systemctl
|
||||
template:
|
||||
src: nym-node.service.j2
|
||||
dest: /etc/systemd/system/nym-node.service
|
||||
notify: Reload systemd
|
||||
|
||||
- name: Enable and start nym service
|
||||
tags: systemctl
|
||||
systemd:
|
||||
name: nym-node
|
||||
enabled: yes
|
||||
state: started
|
||||
daemon_reload: yes
|
||||
@@ -0,0 +1,20 @@
|
||||
{# Priority:
|
||||
1. Use moniker if provided in inventory
|
||||
2. Else strip "nym-exit." prefix if hostname starts with it
|
||||
3. Else use hostname unchanged
|
||||
#}
|
||||
|
||||
{% if moniker is defined and moniker | length > 0 %}
|
||||
{% set moniker_final = moniker %}
|
||||
{% else %}
|
||||
{% if hostname is defined and hostname.startswith('nym-exit.') %}
|
||||
{% set moniker_final = hostname | regex_replace('^nym-exit\\.', '') %}
|
||||
{% else %}
|
||||
{% set moniker_final = hostname %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
moniker = "{{ moniker_final }}"
|
||||
website = " {{ website }}"
|
||||
security_contact = "{{ email }}"
|
||||
details = "{{ description }}"
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Nym Node
|
||||
StartLimitInterval=350
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
User={{ ansible_user }}
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/root/nym-binaries/nym-node run --mode {{ mode }} --accept-operator-terms-and-conditions --wireguard-enabled {{ wireguard_enabled }}
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,49 @@
|
||||
- name: Show which node is being bonded
|
||||
tags: bonding
|
||||
debug:
|
||||
msg: "Bonding Nym node: {{ hostname }}"
|
||||
|
||||
- name: Get bonding details
|
||||
tags: bonding
|
||||
command: "/root/nym-binaries/nym-node bonding-information"
|
||||
register: bondinfo
|
||||
changed_when: false
|
||||
|
||||
- name: Display bonding info
|
||||
tags: bonding
|
||||
debug:
|
||||
msg: "{{ item }}"
|
||||
loop: "{{ bondinfo.stdout_lines }}"
|
||||
|
||||
|
||||
- name: Prompt operator to generate contract message in wallet
|
||||
tags: bonding
|
||||
pause:
|
||||
prompt: |
|
||||
** Using the bonding information above:
|
||||
|
||||
1. Open your wallet
|
||||
2. Go to Bonding section
|
||||
3. Fill Hostname and Identity key from the message printed above
|
||||
4. Copy the CONTRACT MESSAGE that your wallet displays
|
||||
5. Paste it below and press Enter
|
||||
|
||||
Paste CONTRACT MESSAGE here:
|
||||
register: contract_msg_input
|
||||
|
||||
- name: Sign bonding contract message on the node
|
||||
tags: bonding
|
||||
command:
|
||||
argv:
|
||||
- /root/nym-binaries/nym-node
|
||||
- sign
|
||||
- --contract-msg
|
||||
- "{{ contract_msg_input.user_input }}"
|
||||
- --output
|
||||
- json
|
||||
register: sign_output
|
||||
|
||||
- name: Display full signed message exactly as returned
|
||||
tags: bonding
|
||||
debug:
|
||||
msg: "{{ sign_output.stdout }}"
|
||||
@@ -0,0 +1,16 @@
|
||||
- name: Download quic_bridge_deployment.sh
|
||||
tags: quic bridge deployment
|
||||
get_url:
|
||||
url: "{{ quic_bridge_deployment_url }}"
|
||||
dest: "/root/nym-binaries/quic_bridge_deployment.sh"
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure tunnel manager
|
||||
tags: quic bridge deployment
|
||||
become: true
|
||||
command:
|
||||
cmd: "/root/nym-binaries/quic_bridge_deployment.sh {{ item }}"
|
||||
environment:
|
||||
NONINTERACTIVE: "1"
|
||||
loop:
|
||||
- full_bridge_setup
|
||||
@@ -0,0 +1,14 @@
|
||||
- name: Download network-tunnel-manager.sh
|
||||
tags: network tunnel manager
|
||||
get_url:
|
||||
url: "{{ tunnel_manager_url }}"
|
||||
dest: "/root/nym-binaries/network-tunnel-manager.sh"
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure tunnel manager
|
||||
tags: network tunnel manager
|
||||
become: true
|
||||
command:
|
||||
cmd: "/root/nym-binaries/network-tunnel-manager.sh {{ item }}"
|
||||
loop:
|
||||
- complete_networking_configuration
|
||||
@@ -0,0 +1,10 @@
|
||||
nym_binary_dir: /root/nym-binaries
|
||||
nym_binary_path: "{{ nym_binary_dir }}/nym-node"
|
||||
nym_backup_dir: "{{ nym_binary_dir }}/bak"
|
||||
nym_backup_path: "{{ nym_backup_dir }}/nym-node"
|
||||
nym_service_name: nym-node
|
||||
|
||||
# nym_version: "v2025.21-mozzarella"
|
||||
# Optional: set this to pin a specific release tag in (e.g. v2025.21-mozzarella)
|
||||
# otherwise the GitHub “latest” release is used
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
- name: Get latest Nym release metadata
|
||||
uri:
|
||||
url: https://api.github.com/repos/nymtech/nym/releases/latest
|
||||
return_content: yes
|
||||
register: latest_release
|
||||
when: nym_version is not defined and not ansible_check_mode
|
||||
|
||||
- name: Set nym_version from GitHub API
|
||||
set_fact:
|
||||
nym_version: "{{ latest_release.json.tag_name }}"
|
||||
when: nym_version is not defined and not ansible_check_mode
|
||||
|
||||
- name: Show target Nym version tag
|
||||
debug:
|
||||
msg: "Target Nym release tag: {{ nym_version | default('latest (check-mode)') }}"
|
||||
|
||||
- name: Generate binary_url from version
|
||||
set_fact:
|
||||
binary_url: >-
|
||||
https://github.com/nymtech/nym/releases/download/{{ nym_version }}/nym-node
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Download nym-node binary
|
||||
get_url:
|
||||
url: "{{ binary_url }}"
|
||||
dest: "{{ nym_binary_path }}"
|
||||
mode: "0755"
|
||||
register: download_result
|
||||
failed_when: false
|
||||
when: not ansible_check_mode
|
||||
@@ -0,0 +1,122 @@
|
||||
# run --version on the new binary
|
||||
- name: Check new nym-node version
|
||||
command:
|
||||
argv:
|
||||
- "{{ nym_binary_path }}"
|
||||
- --version
|
||||
register: nym_new_version_cmd
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
# show the full stdout so we don’t depend on regex parsing at all
|
||||
# show full upgraded version output, line by line
|
||||
- name: Show upgraded nym-node version info
|
||||
debug:
|
||||
msg: >-
|
||||
{{
|
||||
[
|
||||
"New nym-node --version rc: " ~ (nym_new_version_cmd.rc | default('unset') | string),
|
||||
"New nym-node --version output:"
|
||||
]
|
||||
+ (nym_new_version_cmd.stdout_lines | default([]))
|
||||
}}
|
||||
when: not ansible_check_mode
|
||||
|
||||
# decide if upgrade is successful
|
||||
# success means: the binary executed without an error (rc == 0)
|
||||
- name: Determine if upgrade is successful
|
||||
set_fact:
|
||||
upgrade_ok: "{{ (nym_new_version_cmd.rc | default(1)) == 0 }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
# show the decision for debugging
|
||||
- name: Debug upgrade_ok decision
|
||||
debug:
|
||||
msg:
|
||||
- "upgrade_ok: {{ upgrade_ok }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
#########
|
||||
# success
|
||||
#########
|
||||
|
||||
# show the full version output to the user, line-by-line
|
||||
- name: Show upgraded nym-node version info
|
||||
debug:
|
||||
msg:
|
||||
- "Upgraded nym-node version output:"
|
||||
- "{{ nym_new_version_cmd.stdout_lines | default([]) }}"
|
||||
when: not ansible_check_mode and upgrade_ok | default(false)
|
||||
|
||||
|
||||
# remove backup
|
||||
- name: Remove backup after successful upgrade
|
||||
file:
|
||||
path: "{{ nym_backup_path }}"
|
||||
state: absent
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- upgrade_ok | default(false)
|
||||
- nym_node_bin.stat.exists | default(false)
|
||||
|
||||
# restart service
|
||||
- name: Restart nym-node service after successful upgrade
|
||||
systemd:
|
||||
name: "{{ nym_service_name }}"
|
||||
state: restarted
|
||||
when: not ansible_check_mode and upgrade_ok | default(false)
|
||||
|
||||
# report success
|
||||
- name: Report successful upgrade
|
||||
debug:
|
||||
msg: >-
|
||||
Upgrade successful. nym-node binary executed correctly and the service has been restarted.
|
||||
when: not ansible_check_mode and upgrade_ok | default(false)
|
||||
|
||||
#########
|
||||
# failure
|
||||
#########
|
||||
|
||||
- name: Restore previous nym-node binary after failed upgrade
|
||||
copy:
|
||||
src: "{{ nym_backup_path }}"
|
||||
dest: "{{ nym_binary_path }}"
|
||||
mode: "0755"
|
||||
remote_src: true
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- (upgrade_ok | default(false)) == false
|
||||
- nym_node_bin.stat.exists | default(false)
|
||||
|
||||
- name: Remove backup after rollback
|
||||
file:
|
||||
path: "{{ nym_backup_path }}"
|
||||
state: absent
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- (upgrade_ok | default(false)) == false
|
||||
- nym_node_bin.stat.exists | default(false)
|
||||
|
||||
# always restart the service with the restored binary
|
||||
- name: Restart nym-node service with previous version after failed upgrade
|
||||
systemd:
|
||||
name: "{{ nym_service_name }}"
|
||||
state: restarted
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- (upgrade_ok | default(false)) == false
|
||||
- nym_node_bin.stat.exists | default(false)
|
||||
|
||||
- name: Report failed upgrade and rollback
|
||||
debug:
|
||||
msg: >-
|
||||
Upgrade NOT successful. The previous nym-node binary has been restored
|
||||
and the nym-node service has been restarted with the old version.
|
||||
when: not ansible_check_mode and (upgrade_ok | default(false)) == false
|
||||
|
||||
# optional: hard-fail the play for CI environments
|
||||
#- name: Fail the play to signal upgrade failure
|
||||
# fail:
|
||||
# msg: "nym-node upgrade failed; rolled back to previous binary."
|
||||
# when: not ansible_check_mode and (upgrade_ok | default(false)) == false
|
||||
@@ -0,0 +1,8 @@
|
||||
- name: Prepare for nym-node upgrade (backup, stop service)
|
||||
include_tasks: prepare.yml
|
||||
|
||||
- name: Fetch and install latest nym-node binary
|
||||
include_tasks: fetch_latest.yml
|
||||
|
||||
- name: Verify new nym-node and finalize (restart or rollback)
|
||||
include_tasks: finalize.yml
|
||||
@@ -0,0 +1,69 @@
|
||||
# stop service before touching the binary
|
||||
- name: Stop nym-node service
|
||||
systemd:
|
||||
name: "{{ nym_service_name }}"
|
||||
state: stopped
|
||||
when: not ansible_check_mode
|
||||
|
||||
# check if the current binary exists
|
||||
- name: Check existing nym-node binary
|
||||
stat:
|
||||
path: "{{ nym_binary_path }}"
|
||||
register: nym_node_bin
|
||||
|
||||
# capture current nym-node version (if present)
|
||||
- name: Capture current nym-node version (if present)
|
||||
command:
|
||||
argv:
|
||||
- "{{ nym_binary_path }}"
|
||||
- --version
|
||||
register: nym_current_version_cmd
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
when:
|
||||
- nym_node_bin.stat.exists
|
||||
- not ansible_check_mode
|
||||
|
||||
# show full current version output instead of trying to parse it
|
||||
# show full current version output, line by line
|
||||
- name: Show current nym-node version info
|
||||
debug:
|
||||
msg: >-
|
||||
{{
|
||||
[
|
||||
"Current nym-node --version rc: " ~ (nym_current_version_cmd.rc | default('unset') | string),
|
||||
"Current nym-node --version output:"
|
||||
]
|
||||
+ (nym_current_version_cmd.stdout_lines | default([]))
|
||||
}}
|
||||
when:
|
||||
- nym_node_bin.stat.exists
|
||||
- not ansible_check_mode
|
||||
|
||||
# ensure backup directory exists
|
||||
- name: Ensure backup directory exists
|
||||
file:
|
||||
path: "{{ nym_backup_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
when: not ansible_check_mode
|
||||
|
||||
# backup existing nym-node binary
|
||||
- name: Backup existing nym-node binary
|
||||
copy:
|
||||
src: "{{ nym_binary_path }}"
|
||||
dest: "{{ nym_backup_path }}"
|
||||
remote_src: true
|
||||
mode: "0755"
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- nym_node_bin.stat.exists
|
||||
|
||||
# remove current nym-node binary
|
||||
- name: Remove current nym-node binary
|
||||
file:
|
||||
path: "{{ nym_binary_path }}"
|
||||
state: absent
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- nym_node_bin.stat.exists
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.67"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.67"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
+2
-2
@@ -6,14 +6,14 @@
|
||||
{
|
||||
"name": "exists",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "06e743d143fcc4be20ca2af5e99b19f15d22fff72490473587a14cdc046fda32"
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT * FROM remote_gateway_details WHERE gateway_id_bs58 = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "gateway_id_bs58",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "gateway_owner_address",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "gateway_listener",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes128_ctr_blake3_hmac_keys_bs58",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes256_gcm_siv_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Blob"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "0e85ec18da67cf4e3df04ad80136571f6e920eb2290f20b1b8c5b0ab4b489985"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE remote_gateway_details\n SET\n derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,\n derived_aes256_gcm_siv_key = ?\n WHERE gateway_id_bs58 = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0f1dfb89f1eb39f4a58787af0f53a7a93afb7e4d2e54e2d38fd79d31c8575a54"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO custom_gateway_details(gateway_id_bs58, data) \n VALUES (?, ?)\n ",
|
||||
"query": "\n INSERT INTO custom_gateway_details(gateway_id_bs58, data)\n VALUES (?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,5 +8,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b059bc3688b6b7f83f47048db9897720fd4e6f3211bf74030a9638f7bf6738e4"
|
||||
"hash": "2c113b37864f9fec7e64c0f8fdd38edcdf149acfd38c56a4db3bbf97bdb13210"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n rgd.gateway_id_bs58,\n derived_aes256_gcm_siv_key,\n gateway_listener,\n fallback_listener\n FROM\n remote_gateway_details AS rgd\n INNER JOIN\n remote_gateway_shared_keys AS rgsk\n ON\n rgd.gateway_id_bs58 = rgsk.gateway_id_bs58\n WHERE\n rgd.gateway_id_bs58 = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "gateway_id_bs58",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes256_gcm_siv_key",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "gateway_listener",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "fallback_listener",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4b739e12ea8d917cb17580337caeabb05f0e3ddbec04fdfa111d0fc86ba75505"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO remote_gateway_shared_keys(gateway_id_bs58, derived_aes256_gcm_siv_key)\n VALUES (?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "700a75acbcd90c74baa7823c40739a8ff8a26400c1d2bd45a689970bf1ba0e66"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type) \n VALUES (?, ?, ?)\n ",
|
||||
"query": "\n INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)\n VALUES (?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,5 +8,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8909fd329e7e5fb16c4989b15b3d3a12bba1569520e01f6f074178e23d6ee89e"
|
||||
"hash": "727598e516090da6d26e36d09062b60ccb76d6468f359891428c0bfb96ddd7ef"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO remote_gateway_details(gateway_id_bs58, gateway_listener, fallback_listener)\n VALUES (?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a64a557ba87d4b2c7457857afa7ebc7d4f895fc4991da18ec02c9e250bea0fe0"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)\n VALUES (?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a6939bea03b10cde810a9a099bd597b4f51092e30a41c4085a8f8668f039f7c0"
|
||||
}
|
||||
@@ -9,7 +9,6 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
cosmrs.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
@@ -20,6 +19,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
nym-crypto = { path = "../../crypto", features = ["asymmetric"] }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-gateway-client = { path = "../../client-libs/gateway-client" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE TABLE remote_gateway_details_temp
|
||||
(
|
||||
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY REFERENCES registered_gateway (gateway_id_bs58),
|
||||
derived_aes256_gcm_siv_key BLOB NOT NULL,
|
||||
gateway_listener TEXT NOT NULL,
|
||||
fallback_listener TEXT,
|
||||
expiration_timestamp DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- keep only registrations with a non null aes256 key
|
||||
INSERT INTO remote_gateway_details_temp SELECT gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_listener, NULL, datetime(0, 'unixepoch') FROM remote_gateway_details WHERE derived_aes256_gcm_siv_key IS NOT NULL;
|
||||
|
||||
DROP TABLE remote_gateway_details;
|
||||
ALTER TABLE remote_gateway_details_temp RENAME TO remote_gateway_details;
|
||||
|
||||
-- delete registrations with no key
|
||||
DELETE FROM registered_gateway WHERE gateway_id_bs58 NOT IN ( SELECT gateway_id_bs58 FROM remote_gateway_details);
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
types::{
|
||||
RawActiveGateway, RawCustomGatewayDetails, RawRegisteredGateway, RawRemoteGatewayDetails,
|
||||
},
|
||||
RawGatewayPublishedData,
|
||||
};
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
@@ -144,13 +145,11 @@ impl StorageManager {
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
) -> Result<RawRemoteGatewayDetails, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawRemoteGatewayDetails,
|
||||
"SELECT * FROM remote_gateway_details WHERE gateway_id_bs58 = ?",
|
||||
gateway_id
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
// query_as! macro doesn't use fromRow
|
||||
sqlx::query_as("SELECT * FROM remote_gateway_details WHERE gateway_id_bs58 = ?")
|
||||
.bind(gateway_id)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn set_remote_gateway_details(
|
||||
@@ -159,41 +158,36 @@ impl StorageManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_listener, fallback_listener, expiration_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"#,
|
||||
remote.gateway_id_bs58,
|
||||
remote.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
remote.derived_aes256_gcm_siv_key,
|
||||
remote.gateway_owner_address,
|
||||
remote.gateway_listener,
|
||||
remote.published_data.gateway_listener,
|
||||
remote.published_data.fallback_listener,
|
||||
remote.published_data.expiration_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_remote_gateway_key(
|
||||
pub(crate) async fn update_remote_gateway_published_data(
|
||||
&self,
|
||||
gateway_id_bs58: &str,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&str>,
|
||||
derived_aes256_gcm_siv_key: Option<&[u8]>,
|
||||
published_data: &RawGatewayPublishedData,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE remote_gateway_details
|
||||
SET
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,
|
||||
derived_aes256_gcm_siv_key = ?
|
||||
WHERE gateway_id_bs58 = ?
|
||||
UPDATE remote_gateway_details SET gateway_listener = ?, fallback_listener = ?, expiration_timestamp = ? WHERE gateway_id_bs58 = ?
|
||||
"#,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
published_data.gateway_listener,
|
||||
published_data.fallback_listener,
|
||||
published_data.expiration_timestamp,
|
||||
gateway_id_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
ActiveGateway, BadGateway, GatewayDetails, GatewayRegistration, GatewayType,
|
||||
GatewaysDetailsStore, StorageError,
|
||||
ActiveGateway, BadGateway, GatewayDetails, GatewayPublishedData, GatewayRegistration,
|
||||
GatewayType, GatewaysDetailsStore, StorageError,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use manager::StorageManager;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::path::Path;
|
||||
|
||||
pub mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OnDiskGatewaysDetails {
|
||||
@@ -134,16 +132,15 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
async fn update_gateway_published_data(
|
||||
&self,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.manager
|
||||
.update_remote_gateway_key(
|
||||
.update_remote_gateway_published_data(
|
||||
&gateway_id.to_base58_string(),
|
||||
None,
|
||||
Some(updated_key.as_bytes()),
|
||||
&published_data.into(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
@@ -2,10 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::{ActiveGateway, GatewayRegistration};
|
||||
use crate::{BadGateway, GatewayDetails, GatewaysDetailsStore};
|
||||
use crate::{BadGateway, GatewayDetails, GatewayPublishedData, GatewaysDetailsStore};
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_gateway_requests::{SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -96,26 +95,17 @@ impl GatewaysDetailsStore for InMemGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
async fn update_gateway_published_data(
|
||||
&self,
|
||||
gateway_id: PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
if let Some(target) = guard.gateways.get_mut(&gateway_id.to_string()) {
|
||||
let GatewayDetails::Remote(details) = &mut target.details else {
|
||||
return Ok(());
|
||||
};
|
||||
assert_eq!(Arc::strong_count(&details.shared_key), 1);
|
||||
|
||||
// eh. that's nasty, but it's only ever used for ephemeral clients so should be fine for now...
|
||||
details.shared_key = Arc::new(SharedGatewayKey::Current(
|
||||
SharedSymmetricKey::try_from_bytes(updated_key.as_bytes()).unwrap(),
|
||||
))
|
||||
if let Some(gateway) = guard.gateways.get_mut(&gateway_id.to_base58_string()) {
|
||||
if let GatewayDetails::Remote(ref mut remote_details) = gateway.details {
|
||||
remote_details.published_data = published_data.clone();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,6 @@ pub enum BadGateway {
|
||||
source: Ed25519RecoveryError,
|
||||
},
|
||||
|
||||
#[error("the account owner of gateway {gateway_id} ({raw_owner}) is malformed: {source}")]
|
||||
MalformedGatewayOwnerAccountAddress {
|
||||
gateway_id: String,
|
||||
|
||||
raw_owner: String,
|
||||
|
||||
#[source]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("the shared keys provided for gateway {gateway_id} are malformed: {source}")]
|
||||
MalformedSharedKeys {
|
||||
gateway_id: String,
|
||||
@@ -50,4 +40,12 @@ pub enum BadGateway {
|
||||
#[source]
|
||||
source: url::ParseError,
|
||||
},
|
||||
|
||||
#[error("the listening address ({raw_listener}) is malformed: {source}")]
|
||||
MalformedListenerNoId {
|
||||
raw_listener: String,
|
||||
|
||||
#[source]
|
||||
source: url::ParseError,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod backend;
|
||||
@@ -60,10 +59,11 @@ pub trait GatewaysDetailsStore {
|
||||
details: &GatewayRegistration,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
/// Update the gateway details
|
||||
async fn update_gateway_published_data(
|
||||
&self,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Remove given gateway details from the underlying store.
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::BadGateway;
|
||||
use cosmrs::AccountId;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_gateway_client::client::GatewayListeners;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub const REMOTE_GATEWAY_TYPE: &str = "remote";
|
||||
pub const CUSTOM_GATEWAY_TYPE: &str = "custom";
|
||||
const GATEWAY_DETAILS_TTL: Duration = Duration::days(7);
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ActiveGateway {
|
||||
@@ -65,15 +66,13 @@ impl From<GatewayDetails> for GatewayRegistration {
|
||||
impl GatewayDetails {
|
||||
pub fn new_remote(
|
||||
gateway_id: ed25519::PublicKey,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
gateway_owner_address: Option<AccountId>,
|
||||
gateway_listener: Url,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
published_data: GatewayPublishedData,
|
||||
) -> Self {
|
||||
GatewayDetails::Remote(RemoteGatewayDetails {
|
||||
gateway_id,
|
||||
shared_key,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
published_data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,13 +87,20 @@ impl GatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<&SharedGatewayKey> {
|
||||
pub fn shared_key(&self) -> Option<&SharedSymmetricKey> {
|
||||
match self {
|
||||
GatewayDetails::Remote(details) => Some(&details.shared_key),
|
||||
GatewayDetails::Custom(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn details_exipration(&self) -> Option<OffsetDateTime> {
|
||||
match self {
|
||||
GatewayDetails::Remote(details) => Some(details.published_data.expiration_timestamp),
|
||||
GatewayDetails::Custom(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_custom(&self) -> bool {
|
||||
matches!(self, GatewayDetails::Custom(..))
|
||||
}
|
||||
@@ -164,14 +170,78 @@ pub struct RegisteredGateway {
|
||||
pub gateway_type: GatewayType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayPublishedData {
|
||||
pub listeners: GatewayListeners,
|
||||
pub expiration_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl GatewayPublishedData {
|
||||
pub fn new(listeners: GatewayListeners) -> GatewayPublishedData {
|
||||
GatewayPublishedData {
|
||||
listeners,
|
||||
expiration_timestamp: OffsetDateTime::now_utc() + GATEWAY_DETAILS_TTL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct RawGatewayPublishedData {
|
||||
pub gateway_listener: String,
|
||||
pub fallback_listener: Option<String>,
|
||||
pub expiration_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a GatewayPublishedData> for RawGatewayPublishedData {
|
||||
fn from(value: &'a GatewayPublishedData) -> Self {
|
||||
Self {
|
||||
gateway_listener: value.listeners.primary.to_string(),
|
||||
fallback_listener: value.listeners.fallback.as_ref().map(|uri| uri.to_string()),
|
||||
expiration_timestamp: value.expiration_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RawGatewayPublishedData> for GatewayPublishedData {
|
||||
type Error = BadGateway;
|
||||
|
||||
fn try_from(value: RawGatewayPublishedData) -> Result<Self, Self::Error> {
|
||||
let gateway_listener: Url = Url::parse(&value.gateway_listener).map_err(|source| {
|
||||
BadGateway::MalformedListenerNoId {
|
||||
raw_listener: value.gateway_listener.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
let fallback_listener = value
|
||||
.fallback_listener
|
||||
.as_ref()
|
||||
.map(|uri| {
|
||||
Url::parse(uri).map_err(|source| BadGateway::MalformedListenerNoId {
|
||||
raw_listener: uri.to_owned(),
|
||||
source,
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(GatewayPublishedData {
|
||||
listeners: GatewayListeners {
|
||||
primary: gateway_listener,
|
||||
fallback: fallback_listener,
|
||||
},
|
||||
expiration_timestamp: value.expiration_timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct RawRemoteGatewayDetails {
|
||||
pub gateway_id_bs58: String,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub gateway_owner_address: Option<String>,
|
||||
pub gateway_listener: String,
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
#[zeroize(skip)]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(flatten))]
|
||||
pub published_data: RawGatewayPublishedData,
|
||||
}
|
||||
|
||||
impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
@@ -186,81 +256,26 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
}
|
||||
})?;
|
||||
|
||||
let shared_key =
|
||||
match (
|
||||
&value.derived_aes256_gcm_siv_key,
|
||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
) {
|
||||
(None, None) => {
|
||||
return Err(BadGateway::MissingSharedKey {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
})
|
||||
}
|
||||
(Some(aes256gcm_siv), _) => {
|
||||
let current_key =
|
||||
SharedSymmetricKey::try_from_bytes(aes256gcm_siv).map_err(|source| {
|
||||
BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
SharedGatewayKey::Current(current_key)
|
||||
}
|
||||
(None, Some(aes128ctr_hmac)) => {
|
||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
})?;
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
}
|
||||
};
|
||||
|
||||
let gateway_owner_address = value
|
||||
.gateway_owner_address
|
||||
.as_ref()
|
||||
.map(|raw_owner| {
|
||||
AccountId::from_str(raw_owner).map_err(|source| {
|
||||
BadGateway::MalformedGatewayOwnerAccountAddress {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
raw_owner: raw_owner.clone(),
|
||||
source,
|
||||
}
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let gateway_listener = Url::parse(&value.gateway_listener).map_err(|source| {
|
||||
BadGateway::MalformedListener {
|
||||
let shared_key = SharedSymmetricKey::try_from_bytes(&value.derived_aes256_gcm_siv_key)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
raw_listener: value.gateway_listener.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
|
||||
Ok(RemoteGatewayDetails {
|
||||
gateway_id,
|
||||
shared_key: Arc::new(shared_key),
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
published_data: value.published_data.clone().try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
|
||||
fn from(value: &'a RemoteGatewayDetails) -> Self {
|
||||
let (derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) =
|
||||
match value.shared_key.deref() {
|
||||
SharedGatewayKey::Current(key) => (None, Some(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
|
||||
};
|
||||
|
||||
RawRemoteGatewayDetails {
|
||||
gateway_id_bs58: value.gateway_id.to_base58_string(),
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address: value.gateway_owner_address.as_ref().map(|o| o.to_string()),
|
||||
gateway_listener: value.gateway_listener.to_string(),
|
||||
derived_aes256_gcm_siv_key: value.shared_key.to_bytes(),
|
||||
published_data: (&value.published_data).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,11 +284,9 @@ impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
|
||||
pub struct RemoteGatewayDetails {
|
||||
pub gateway_id: ed25519::PublicKey,
|
||||
|
||||
pub shared_key: Arc<SharedGatewayKey>,
|
||||
pub shared_key: Arc<SharedSymmetricKey>,
|
||||
|
||||
pub gateway_owner_address: Option<AccountId>,
|
||||
|
||||
pub gateway_listener: Url,
|
||||
pub published_data: GatewayPublishedData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -87,6 +87,7 @@ where
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(common_args.latency_based_selection),
|
||||
common_args.force_tls_gateway,
|
||||
false,
|
||||
);
|
||||
tracing::debug!("Gateway selection specification: {selection_spec:?}");
|
||||
|
||||
@@ -167,6 +168,7 @@ where
|
||||
identity: gateway_details.gateway_id,
|
||||
active: common_args.set_active,
|
||||
typ: gateway_registration.details.typ().to_string(),
|
||||
endpoint: Some(gateway_details.gateway_listener.clone()),
|
||||
endpoint: Some(gateway_details.published_data.listeners.primary.clone()),
|
||||
fallback_endpoint: gateway_details.published_data.listeners.fallback.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ where
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(common_args.latency_based_selection),
|
||||
common_args.force_tls_gateway,
|
||||
false,
|
||||
);
|
||||
tracing::debug!("Gateway selection specification: {selection_spec:?}");
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ where
|
||||
identity: remote_details.gateway_id,
|
||||
active: active_gateway == Some(remote_details.gateway_id),
|
||||
typ: GatewayType::Remote.to_string(),
|
||||
endpoint: Some(remote_details.gateway_listener),
|
||||
endpoint: Some(remote_details.published_data.listeners.primary.clone()),
|
||||
fallback_endpoint: remote_details.published_data.listeners.fallback.clone(),
|
||||
}),
|
||||
GatewayDetails::Custom(_) => info.push(GatewayInfo {
|
||||
registration: gateway.registration_timestamp,
|
||||
@@ -64,6 +65,7 @@ where
|
||||
active: active_gateway == Some(gateway.details.gateway_id()),
|
||||
typ: gateway.details.typ().to_string(),
|
||||
endpoint: None,
|
||||
fallback_endpoint: None,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct GatewayInfo {
|
||||
|
||||
pub typ: String,
|
||||
pub endpoint: Option<Url>,
|
||||
pub fallback_endpoint: Option<Url>,
|
||||
}
|
||||
|
||||
impl Display for GatewayInfo {
|
||||
@@ -30,6 +31,9 @@ impl Display for GatewayInfo {
|
||||
if let Some(endpoint) = &self.endpoint {
|
||||
write!(f, " endpoint: {endpoint}")?;
|
||||
}
|
||||
if let Some(fallback_endpoint) = &self.fallback_endpoint {
|
||||
write!(f, " fallback: {fallback_endpoint}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +529,6 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -555,14 +554,7 @@ where
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
)
|
||||
} else {
|
||||
let cfg = GatewayConfig::new(
|
||||
details.gateway_id,
|
||||
details
|
||||
.gateway_owner_address
|
||||
.as_ref()
|
||||
.map(|o| o.to_string()),
|
||||
details.gateway_listener.to_string(),
|
||||
);
|
||||
let cfg = GatewayConfig::new(details.gateway_id, details.published_data.listeners);
|
||||
GatewayClient::new(
|
||||
GatewayClientConfig::new_default()
|
||||
.with_disabled_credentials_mode(config.client.disabled_credentials_mode)
|
||||
@@ -592,32 +584,13 @@ where
|
||||
// the gateway client startup procedure is slightly more complicated now
|
||||
// we need to:
|
||||
// - perform handshake (reg or auth)
|
||||
// - check for key upgrade
|
||||
// - maybe perform another upgrade handshake
|
||||
// - check for bandwidth
|
||||
// - start background tasks
|
||||
let auth_res = gateway_client
|
||||
let _ = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
if auth_res.requires_key_upgrade {
|
||||
// drop the shared_key arc because we don't need it and we can't hold it for the purposes of upgrade
|
||||
drop(auth_res);
|
||||
|
||||
let updated_key = gateway_client
|
||||
.upgrade_key_authenticated()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
details_store
|
||||
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
|
||||
.await.map_err(|err| {
|
||||
tracing::error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
||||
})?
|
||||
}
|
||||
|
||||
gateway_client
|
||||
.claim_initial_bandwidth()
|
||||
.await
|
||||
@@ -636,7 +609,6 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -667,7 +639,6 @@ where
|
||||
config,
|
||||
initialisation_result,
|
||||
bandwidth_controller,
|
||||
details_store,
|
||||
packet_router,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
@@ -975,8 +946,7 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (reply_storage_backend, credential_store, details_store) =
|
||||
self.client_store.into_runtime_stores();
|
||||
let (reply_storage_backend, credential_store, _) = self.client_store.into_runtime_stores();
|
||||
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -1069,7 +1039,6 @@ where
|
||||
&self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
&details_store,
|
||||
gateway_packet_router,
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{ActiveGateway, GatewayRegistration, GatewaysDetailsStore};
|
||||
use nym_client_core_gateways_storage::{
|
||||
ActiveGateway, GatewayPublishedData, GatewayRegistration, GatewaysDetailsStore,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
|
||||
// helpers for error wrapping
|
||||
@@ -85,6 +87,23 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_stored_published_data_gateway<D>(
|
||||
details_store: &D,
|
||||
gateway_id: &ed25519::PublicKey,
|
||||
published_data: &GatewayPublishedData,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
D: GatewaysDetailsStore,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
details_store
|
||||
.update_gateway_published_data(gateway_id, published_data)
|
||||
.await
|
||||
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
|
||||
source: Box::new(source),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_active_gateway_details<D>(
|
||||
details_store: &D,
|
||||
) -> Result<ActiveGateway, ClientCoreError>
|
||||
|
||||
@@ -2,210 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1_1_33 {
|
||||
use crate::client::base_client::{
|
||||
non_wasm_helpers::setup_fs_gateways_storage,
|
||||
storage::helpers::{set_active_gateway, store_gateway_details},
|
||||
};
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::disk_persistence::CommonClientPaths;
|
||||
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{
|
||||
CustomGatewayDetails, GatewayDetails, GatewayRegistration, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_gateway_requests::shared_key::LegacySharedKeys;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{digest::Digest, Sha256};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
mod base64 {
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&STANDARD.encode(bytes))
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<u8>, D::Error> {
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
STANDARD.decode(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum PersistedGatewayDetails {
|
||||
/// Standard details of a remote gateway
|
||||
Default(PersistedGatewayConfig),
|
||||
|
||||
/// Custom gateway setup, such as for a client embedded inside gateway itself
|
||||
Custom(PersistedCustomGatewayDetails),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
struct PersistedGatewayConfig {
|
||||
/// The hash of the shared keys to ensure the correct ones are used with those gateway details.
|
||||
#[serde(with = "base64")]
|
||||
key_hash: Vec<u8>,
|
||||
|
||||
/// Actual gateway details being persisted.
|
||||
details: OldGatewayEndpointConfigV1_1_33,
|
||||
}
|
||||
|
||||
impl PersistedGatewayConfig {
|
||||
fn verify(&self, shared_key: &LegacySharedKeys) -> bool {
|
||||
let key_bytes = Zeroizing::new(shared_key.to_bytes());
|
||||
|
||||
let mut key_hasher = Sha256::new();
|
||||
key_hasher.update(&key_bytes);
|
||||
let key_hash = key_hasher.finalize();
|
||||
|
||||
self.key_hash == key_hash.deref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct PersistedCustomGatewayDetails {
|
||||
gateway_id: String,
|
||||
}
|
||||
|
||||
fn load_shared_key<P: AsRef<Path>>(path: P) -> Result<LegacySharedKeys, ClientCoreError> {
|
||||
// the shared key was a simple pem file
|
||||
Ok(nym_pemstore::load_key(path)?)
|
||||
}
|
||||
|
||||
fn gateway_details_from_raw(
|
||||
gateway_id: String,
|
||||
gateway_owner: String,
|
||||
gateway_listener: String,
|
||||
gateway_shared_key: LegacySharedKeys,
|
||||
) -> Result<GatewayDetails, ClientCoreError> {
|
||||
Ok(GatewayDetails::Remote(RemoteGatewayDetails {
|
||||
gateway_id: gateway_id
|
||||
.parse()
|
||||
.map_err(|err| ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
})?,
|
||||
shared_key: Arc::new(gateway_shared_key.into()),
|
||||
gateway_owner_address: Some(gateway_owner.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway owner address was malformed: {err}"),
|
||||
}
|
||||
})?),
|
||||
gateway_listener: gateway_listener.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway listener address was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
}))
|
||||
}
|
||||
|
||||
// helper to extract shared key and gateway details into the new GatewayRegistration
|
||||
fn extract_gateway_registration(
|
||||
storage_paths: &CommonClientPathsV1_1_33,
|
||||
) -> Result<GatewayRegistration, ClientCoreError> {
|
||||
let details_file = std::fs::File::open(&storage_paths.gateway_details).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to open gateway details file at {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
// in v1.1.33 of the clients, the gateway details struct was saved as json
|
||||
let details: PersistedGatewayDetails =
|
||||
serde_json::from_reader(details_file).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to deserialize gateway details from {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
let details = match details {
|
||||
PersistedGatewayDetails::Default(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
if !config.verify(&gateway_shared_key) {
|
||||
return Err(ClientCoreError::UpgradeFailure {
|
||||
message: "failed to verify consistency of the existing gateway details"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
gateway_details_from_raw(
|
||||
config.details.gateway_id,
|
||||
config.details.gateway_owner,
|
||||
config.details.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
}
|
||||
PersistedGatewayDetails::Custom(custom) => {
|
||||
GatewayDetails::Custom(CustomGatewayDetails {
|
||||
gateway_id: custom.gateway_id.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(details.into())
|
||||
}
|
||||
|
||||
// it's responsibility of the caller to ensure this is called **after** new registration has already been saved
|
||||
fn remove_old_gateway_details(storage_paths: &CommonClientPathsV1_1_33) -> std::io::Result<()> {
|
||||
std::fs::remove_file(&storage_paths.gateway_details)?;
|
||||
|
||||
if storage_paths.keys.gateway_shared_key_file.exists() {
|
||||
std::fs::remove_file(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_gateway_details(
|
||||
old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
new_storage_paths: &CommonClientPaths,
|
||||
preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
_old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
_new_storage_paths: &CommonClientPaths,
|
||||
_preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let gateway_registration = match preloaded_config {
|
||||
Some(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&old_storage_paths.keys.gateway_shared_key_file)?;
|
||||
gateway_details_from_raw(
|
||||
config.gateway_id,
|
||||
config.gateway_owner,
|
||||
config.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
.into()
|
||||
}
|
||||
None => extract_gateway_registration(old_storage_paths)?,
|
||||
};
|
||||
|
||||
// since we're migrating to a brand new store, the store should be empty
|
||||
// and thus set the 'new' gateway as the active one
|
||||
let details_store =
|
||||
setup_fs_gateways_storage(&new_storage_paths.gateway_registrations).await?;
|
||||
store_gateway_details(&details_store, &gateway_registration).await?;
|
||||
set_active_gateway(
|
||||
&details_store,
|
||||
&gateway_registration.details.gateway_id().to_base58_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
remove_old_gateway_details(old_storage_paths).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("failed to remove old data: {err}"),
|
||||
}
|
||||
})
|
||||
Err(ClientCoreError::UnsupportedMigration(
|
||||
"migration of legacy keys has been removed and is no longer supported".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,12 @@ where
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Average delay a forward packet is going to get delayed at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
/// Defines configuration options related to cover traffic.
|
||||
cover_traffic: config::CoverTraffic,
|
||||
|
||||
@@ -122,6 +125,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
LoopCoverTrafficStream {
|
||||
ack_key,
|
||||
average_ack_delay,
|
||||
average_packet_delay: traffic_config.average_packet_delay,
|
||||
cover_traffic: cover_config,
|
||||
next_delay,
|
||||
mix_tx,
|
||||
@@ -187,7 +191,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
&self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.average_ack_delay,
|
||||
self.cover_traffic.loop_cover_traffic_average_delay,
|
||||
self.average_packet_delay,
|
||||
cover_traffic_packet_size,
|
||||
self.packet_type,
|
||||
) {
|
||||
|
||||
@@ -6,7 +6,7 @@ use nym_crypto::{
|
||||
asymmetric::{ed25519, x25519},
|
||||
hkdf::{DerivationMaterial, InvalidLength},
|
||||
};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
@@ -106,7 +106,5 @@ fn _assert_keys_zeroize_on_drop() {
|
||||
_assert_zeroize_on_drop::<ed25519::KeyPair>();
|
||||
_assert_zeroize_on_drop::<x25519::KeyPair>();
|
||||
_assert_zeroize_on_drop::<AckKey>();
|
||||
_assert_zeroize_on_drop::<LegacySharedKeys>();
|
||||
_assert_zeroize_on_drop::<SharedSymmetricKey>();
|
||||
_assert_zeroize_on_drop::<SharedGatewayKey>();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ use std::path::PathBuf;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreError {
|
||||
#[error("could not perform the state migration: {0}")]
|
||||
UnsupportedMigration(String),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
@@ -43,6 +46,9 @@ pub enum ClientCoreError {
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidUrl(String),
|
||||
|
||||
#[error("node doesn't advertise ip addresses : {0}")]
|
||||
MissingIpAddress(String),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
|
||||
@@ -163,6 +169,9 @@ pub enum ClientCoreError {
|
||||
#[error("custom selection of gateway was expected")]
|
||||
CustomGatewaySelectionExpected,
|
||||
|
||||
#[error("custom selection of gateway was unexpected")]
|
||||
UnexpectedCustomGatewaySelection,
|
||||
|
||||
#[error("the persisted gateway details were set for a custom setup")]
|
||||
UnexpectedPersistedCustomGatewayDetails,
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::error::ClientCoreError;
|
||||
use crate::init::types::RegistrationResult;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::client::GatewayListeners;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
|
||||
@@ -379,12 +380,12 @@ pub(super) fn get_specified_gateway(
|
||||
|
||||
pub(super) async fn register_with_gateway(
|
||||
gateway_id: ed25519::PublicKey,
|
||||
gateway_listener: Url,
|
||||
gateway_listeners: GatewayListeners,
|
||||
our_identity: Arc<ed25519::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<RegistrationResult, ClientCoreError> {
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway_listener,
|
||||
gateway_listeners,
|
||||
gateway_id,
|
||||
our_identity.clone(),
|
||||
#[cfg(unix)]
|
||||
@@ -409,14 +410,6 @@ pub(super) async fn register_with_gateway(
|
||||
}
|
||||
})?;
|
||||
|
||||
// this should NEVER happen, if it did, it means the function was misused,
|
||||
// because for any fresh **registration**, the derived key is always up to date
|
||||
if auth_response.requires_key_upgrade {
|
||||
return Err(ClientCoreError::UnexpectedKeyUpgrade {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(RegistrationResult {
|
||||
shared_keys: auth_response.initial_shared_key,
|
||||
authenticated_ephemeral_client: gateway_client,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use crate::client::base_client::storage::helpers::{
|
||||
has_gateway_details, load_active_gateway_details, load_client_keys, load_gateway_details,
|
||||
store_gateway_details,
|
||||
store_gateway_details, update_stored_published_data_gateway,
|
||||
};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
@@ -16,8 +16,8 @@ use crate::init::helpers::{
|
||||
use crate::init::types::{
|
||||
GatewaySelectionSpecification, GatewaySetup, InitialisationResult, SelectedGateway,
|
||||
};
|
||||
use nym_client_core_gateways_storage::GatewaysDetailsStore;
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewayRegistration};
|
||||
use nym_client_core_gateways_storage::{GatewayPublishedData, GatewaysDetailsStore};
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -71,21 +71,28 @@ where
|
||||
let mut rng = OsRng;
|
||||
|
||||
let selected_gateway = match selection_specification {
|
||||
GatewaySelectionSpecification::UniformRemote { must_use_tls } => {
|
||||
GatewaySelectionSpecification::UniformRemote {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
} => {
|
||||
let gateway = uniformly_random_gateway(&mut rng, &available_gateways, must_use_tls)?;
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls)?
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
|
||||
}
|
||||
GatewaySelectionSpecification::RemoteByLatency { must_use_tls } => {
|
||||
GatewaySelectionSpecification::RemoteByLatency {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
} => {
|
||||
let gateway =
|
||||
choose_gateway_by_latency(&mut rng, &available_gateways, must_use_tls).await?;
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls)?
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
|
||||
}
|
||||
GatewaySelectionSpecification::Specified {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
identity,
|
||||
} => {
|
||||
let gateway = get_specified_gateway(&identity, &available_gateways, must_use_tls)?;
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls)?
|
||||
SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?
|
||||
}
|
||||
GatewaySelectionSpecification::Custom {
|
||||
gateway_identity,
|
||||
@@ -105,15 +112,15 @@ where
|
||||
let (gateway_details, authenticated_ephemeral_client) = match selected_gateway {
|
||||
SelectedGateway::Remote {
|
||||
gateway_id,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
|
||||
gateway_listeners,
|
||||
} => {
|
||||
// if we're using a 'normal' gateway setup, do register
|
||||
let our_identity = client_keys.identity_keypair();
|
||||
|
||||
let registration = helpers::register_with_gateway(
|
||||
gateway_id,
|
||||
gateway_listener.clone(),
|
||||
gateway_listeners.clone(),
|
||||
our_identity,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
@@ -123,8 +130,7 @@ where
|
||||
GatewayDetails::new_remote(
|
||||
gateway_id,
|
||||
registration.shared_keys,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
GatewayPublishedData::new(gateway_listeners),
|
||||
),
|
||||
Some(registration.authenticated_ephemeral_client),
|
||||
)
|
||||
@@ -150,6 +156,46 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn refresh_gateway_published_data<D>(
|
||||
details_store: &D,
|
||||
registration: GatewayRegistration,
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
D: GatewaysDetailsStore,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let gateway_id = registration.gateway_id().to_base58_string();
|
||||
tracing::trace!("Updating gateway details : {gateway_id}");
|
||||
|
||||
let gateway = get_specified_gateway(&gateway_id, &available_gateways, must_use_tls)?;
|
||||
let selected_gateway = SelectedGateway::from_topology_node(gateway, must_use_tls, no_hostname)?;
|
||||
|
||||
let new_gateway_listeners = match selected_gateway {
|
||||
SelectedGateway::Remote {
|
||||
gateway_listeners, ..
|
||||
} => gateway_listeners,
|
||||
SelectedGateway::Custom { .. } => {
|
||||
// this should not happen, as `from_topology_node` returns a Remote
|
||||
Err(ClientCoreError::UnexpectedCustomGatewaySelection)?
|
||||
}
|
||||
};
|
||||
|
||||
let new_published_data = GatewayPublishedData::new(new_gateway_listeners);
|
||||
|
||||
// update gateway details
|
||||
update_stored_published_data_gateway(
|
||||
details_store,
|
||||
®istration.gateway_id(),
|
||||
&new_published_data,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn use_loaded_gateway_details<K, D>(
|
||||
key_store: &K,
|
||||
details_store: &D,
|
||||
|
||||
@@ -10,12 +10,11 @@ use nym_client_core_gateways_storage::{
|
||||
GatewayRegistration, GatewaysDetailsStore, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_client::client::{GatewayListeners, InitGatewayClient};
|
||||
use nym_gateway_client::SharedSymmetricKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Debug, Display};
|
||||
#[cfg(unix)]
|
||||
@@ -28,9 +27,7 @@ pub enum SelectedGateway {
|
||||
Remote {
|
||||
gateway_id: ed25519::PublicKey,
|
||||
|
||||
gateway_owner_address: Option<AccountId>,
|
||||
|
||||
gateway_listener: Url,
|
||||
gateway_listeners: GatewayListeners,
|
||||
},
|
||||
Custom {
|
||||
gateway_id: ed25519::PublicKey,
|
||||
@@ -42,24 +39,40 @@ impl SelectedGateway {
|
||||
pub fn from_topology_node(
|
||||
node: RoutingNode,
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
) -> Result<Self, ClientCoreError> {
|
||||
// for now, let's use 'old' behaviour, if you want to change it, you can pass it up the enum stack yourself : )
|
||||
let prefer_ipv6 = false;
|
||||
|
||||
let gateway_listener = if must_use_tls {
|
||||
node.ws_entry_address_tls()
|
||||
.ok_or(ClientCoreError::UnsupportedWssProtocol {
|
||||
gateway: node.identity_key.to_base58_string(),
|
||||
})?
|
||||
let (gateway_listener, fallback_listener) = if must_use_tls {
|
||||
// WSS main, no fallback
|
||||
let primary =
|
||||
node.ws_entry_address_tls()
|
||||
.ok_or(ClientCoreError::UnsupportedWssProtocol {
|
||||
gateway: node.identity_key.to_base58_string(),
|
||||
})?;
|
||||
(primary, None)
|
||||
} else {
|
||||
node.ws_entry_address(prefer_ipv6)
|
||||
.ok_or(ClientCoreError::UnsupportedEntry {
|
||||
let (maybe_primary, fallback) =
|
||||
node.ws_entry_address_with_fallback(prefer_ipv6, no_hostname);
|
||||
(
|
||||
maybe_primary.ok_or(ClientCoreError::UnsupportedEntry {
|
||||
id: node.node_id,
|
||||
identity: node.identity_key.to_base58_string(),
|
||||
})?
|
||||
})?,
|
||||
fallback,
|
||||
)
|
||||
};
|
||||
|
||||
let gateway_listener =
|
||||
let fallback_listener_url = fallback_listener.and_then(|address| {
|
||||
Url::parse(&address)
|
||||
.inspect_err(|err| {
|
||||
tracing::warn!("Malformed fallback listener, none will be used : {err}")
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
let gateway_listener_url =
|
||||
Url::parse(&gateway_listener).map_err(|source| ClientCoreError::MalformedListener {
|
||||
gateway_id: node.identity_key.to_base58_string(),
|
||||
raw_listener: gateway_listener,
|
||||
@@ -68,8 +81,10 @@ impl SelectedGateway {
|
||||
|
||||
Ok(SelectedGateway::Remote {
|
||||
gateway_id: node.identity_key,
|
||||
gateway_owner_address: None,
|
||||
gateway_listener,
|
||||
gateway_listeners: GatewayListeners {
|
||||
primary: gateway_listener_url,
|
||||
fallback: fallback_listener_url,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,7 +113,7 @@ impl SelectedGateway {
|
||||
/// - shared keys derived between ourselves and the node
|
||||
/// - an authenticated handle of an ephemeral handle created for the purposes of registration
|
||||
pub struct RegistrationResult {
|
||||
pub shared_keys: Arc<SharedGatewayKey>,
|
||||
pub shared_keys: Arc<SharedSymmetricKey>,
|
||||
pub authenticated_ephemeral_client: InitGatewayClient,
|
||||
}
|
||||
|
||||
@@ -145,20 +160,36 @@ impl InitialisationResult {
|
||||
pub fn gateway_id(&self) -> ed25519::PublicKey {
|
||||
self.gateway_registration.details.gateway_id()
|
||||
}
|
||||
|
||||
// indicates if the remote gateway details TTL has expired
|
||||
pub fn exipred_details(&self) -> bool {
|
||||
if let Some(expiration_timestamp) = self.gateway_registration.details.details_exipration() {
|
||||
OffsetDateTime::now_utc() > expiration_timestamp
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum GatewaySelectionSpecification {
|
||||
/// Uniformly choose a random remote gateway.
|
||||
UniformRemote { must_use_tls: bool },
|
||||
UniformRemote {
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
},
|
||||
|
||||
/// Should the new, remote, gateway be selected based on latency.
|
||||
RemoteByLatency { must_use_tls: bool },
|
||||
RemoteByLatency {
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
},
|
||||
|
||||
/// Gateway with this specific identity should be chosen.
|
||||
// JS: I don't really like the name of this enum variant but couldn't think of anything better at the time
|
||||
Specified {
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
identity: IdentityKey,
|
||||
},
|
||||
|
||||
@@ -174,6 +205,7 @@ impl Default for GatewaySelectionSpecification {
|
||||
fn default() -> Self {
|
||||
GatewaySelectionSpecification::UniformRemote {
|
||||
must_use_tls: false,
|
||||
no_hostname: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,16 +215,24 @@ impl GatewaySelectionSpecification {
|
||||
gateway_identity: Option<String>,
|
||||
latency_based_selection: Option<bool>,
|
||||
must_use_tls: bool,
|
||||
no_hostname: bool,
|
||||
) -> Self {
|
||||
if let Some(identity) = gateway_identity {
|
||||
GatewaySelectionSpecification::Specified {
|
||||
identity,
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
}
|
||||
} else if let Some(true) = latency_based_selection {
|
||||
GatewaySelectionSpecification::RemoteByLatency { must_use_tls }
|
||||
GatewaySelectionSpecification::RemoteByLatency {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
}
|
||||
} else {
|
||||
GatewaySelectionSpecification::UniformRemote { must_use_tls }
|
||||
GatewaySelectionSpecification::UniformRemote {
|
||||
must_use_tls,
|
||||
no_hostname,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,6 +355,7 @@ pub struct InitResults {
|
||||
pub encryption_key: String,
|
||||
pub gateway_id: String,
|
||||
pub gateway_listener: String,
|
||||
pub fallback_listener: Option<String>,
|
||||
pub gateway_registration: OffsetDateTime,
|
||||
pub address: Recipient,
|
||||
}
|
||||
@@ -332,7 +373,13 @@ impl InitResults {
|
||||
identity_key: address.identity().to_base58_string(),
|
||||
encryption_key: address.encryption_key().to_base58_string(),
|
||||
gateway_id: gateway.gateway_id.to_base58_string(),
|
||||
gateway_listener: gateway.gateway_listener.to_string(),
|
||||
gateway_listener: gateway.published_data.listeners.primary.to_string(),
|
||||
fallback_listener: gateway
|
||||
.published_data
|
||||
.listeners
|
||||
.fallback
|
||||
.as_ref()
|
||||
.map(|uri| uri.to_string()),
|
||||
gateway_registration: registration,
|
||||
address,
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BandwidthResponse, BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersion,
|
||||
GatewayProtocolVersionExt, GatewayRequestsError, SensitiveServerResponse, ServerResponse,
|
||||
SharedGatewayKey, SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
GatewayProtocolVersionExt, GatewayRequestsError, ServerResponse, SharedSymmetricKey,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
@@ -47,43 +47,39 @@ use std::os::raw::c_int as RawFd;
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::sleep;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub mod config;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod websockets;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use websockets::connect_async;
|
||||
use crate::client::websockets::connect_async_with_fallback;
|
||||
|
||||
pub struct GatewayConfig {
|
||||
pub gateway_identity: ed25519::PublicKey,
|
||||
|
||||
// currently a dead field
|
||||
pub gateway_owner: Option<String>,
|
||||
|
||||
pub gateway_listener: String,
|
||||
pub gateway_listeners: GatewayListeners,
|
||||
}
|
||||
|
||||
impl GatewayConfig {
|
||||
pub fn new(
|
||||
gateway_identity: ed25519::PublicKey,
|
||||
gateway_owner: Option<String>,
|
||||
gateway_listener: String,
|
||||
) -> Self {
|
||||
pub fn new(gateway_identity: ed25519::PublicKey, gateway_listeners: GatewayListeners) -> Self {
|
||||
GatewayConfig {
|
||||
gateway_identity,
|
||||
gateway_owner,
|
||||
gateway_listener,
|
||||
gateway_listeners,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayListeners {
|
||||
pub primary: Url,
|
||||
pub fallback: Option<Url>,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct AuthenticationResponse {
|
||||
pub initial_shared_key: Arc<SharedGatewayKey>,
|
||||
pub requires_key_upgrade: bool,
|
||||
pub initial_shared_key: Arc<SharedSymmetricKey>,
|
||||
}
|
||||
|
||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||
@@ -92,10 +88,10 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
|
||||
authenticated: bool,
|
||||
bandwidth: ClientBandwidth,
|
||||
gateway_address: String,
|
||||
gateway_addresses: GatewayListeners,
|
||||
gateway_identity: ed25519::PublicKey,
|
||||
local_identity: Arc<ed25519::KeyPair>,
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
@@ -118,7 +114,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
gateway_config: GatewayConfig,
|
||||
local_identity: Arc<ed25519::KeyPair>,
|
||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
@@ -129,7 +125,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
cfg,
|
||||
authenticated: false,
|
||||
bandwidth: ClientBandwidth::new_empty(),
|
||||
gateway_address: gateway_config.gateway_listener,
|
||||
gateway_addresses: gateway_config.gateway_listeners,
|
||||
gateway_identity: gateway_config.gateway_identity,
|
||||
local_identity,
|
||||
shared_key,
|
||||
@@ -148,7 +144,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedGatewayKey>> {
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedSymmetricKey>> {
|
||||
self.shared_key.clone()
|
||||
}
|
||||
|
||||
@@ -203,12 +199,19 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!(
|
||||
"Attempting to establish connection to gateway at: {}",
|
||||
self.gateway_address
|
||||
);
|
||||
let (ws_stream, _) = connect_async(
|
||||
&self.gateway_address,
|
||||
if let Some(fallback_url) = &self.gateway_addresses.fallback {
|
||||
debug!(
|
||||
"Attempting to establish connection to gateway at: {}, with fallback at: {fallback_url}",
|
||||
self.gateway_addresses.primary
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Attempting to establish connection to gateway at: {}",
|
||||
self.gateway_addresses.primary
|
||||
);
|
||||
}
|
||||
let (ws_stream, _) = connect_async_with_fallback(
|
||||
&self.gateway_addresses,
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback.clone(),
|
||||
)
|
||||
@@ -221,7 +224,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
let ws_stream = match JSWebsocket::new(&self.gateway_address) {
|
||||
let ws_stream = match JSWebsocket::new(self.gateway_addresses.primary.as_ref()) {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(e) => {
|
||||
return Err(GatewayClientError::NetworkErrorWasm(e));
|
||||
@@ -274,7 +277,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Some(shared_key) = self.shared_key() {
|
||||
let encrypted = message.encrypt(&*shared_key)?;
|
||||
let encrypted = message.encrypt(&shared_key)?;
|
||||
Box::pin(self.send_websocket_message_without_response(encrypted)).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -463,19 +466,14 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
async fn register(
|
||||
&mut self,
|
||||
supported_gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
supported_gateway_protocol: GatewayProtocolVersion,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
let derive_aes256_gcm_siv_key = supported_gateway_protocol.supports_aes256_gcm_siv();
|
||||
|
||||
debug_assert!(self.connection.is_available());
|
||||
log::debug!(
|
||||
"registering with gateway. using legacy key derivation: {}",
|
||||
!derive_aes256_gcm_siv_key
|
||||
);
|
||||
log::debug!("registering with gateway");
|
||||
|
||||
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
||||
// and putting it into the GatewayClient struct would be a hassle
|
||||
@@ -525,75 +523,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upgrade_key_authenticated(
|
||||
&mut self,
|
||||
) -> Result<Zeroizing<SharedSymmetricKey>, GatewayClientError> {
|
||||
info!("*** STARTING AES128CTR-HMAC KEY UPGRADE INTO AES256GCM-SIV***");
|
||||
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
if !shared_key.is_legacy() {
|
||||
return Err(GatewayClientError::KeyAlreadyUpgraded);
|
||||
}
|
||||
|
||||
// make sure we have the only reference, so we could safely swap it
|
||||
if Arc::strong_count(shared_key) != 1 {
|
||||
return Err(GatewayClientError::KeyAlreadyInUse);
|
||||
}
|
||||
|
||||
assert!(shared_key.is_legacy());
|
||||
let legacy_key = shared_key.unwrap_legacy();
|
||||
let (updated_key, hkdf_salt) = legacy_key.upgrade();
|
||||
let derived_key_digest = updated_key.digest();
|
||||
|
||||
let upgrade_request = ClientRequest::UpgradeKey {
|
||||
hkdf_salt,
|
||||
derived_key_digest,
|
||||
}
|
||||
.encrypt(legacy_key)?;
|
||||
|
||||
info!("sending upgrade request and awaiting the acknowledgement back");
|
||||
let (ciphertext, nonce) = match self
|
||||
.send_websocket_message_with_response(upgrade_request)
|
||||
.await?
|
||||
{
|
||||
ServerResponse::EncryptedResponse { ciphertext, nonce } => (ciphertext, nonce),
|
||||
ServerResponse::Error { message } => {
|
||||
return Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
};
|
||||
|
||||
// attempt to decrypt it using NEW key
|
||||
let Ok(response) = SensitiveServerResponse::decrypt(&ciphertext, &nonce, &updated_key)
|
||||
else {
|
||||
return Err(GatewayClientError::FatalKeyUpgradeFailure);
|
||||
};
|
||||
|
||||
match response {
|
||||
SensitiveServerResponse::KeyUpgradeAck { .. } => {
|
||||
info!("received key upgrade acknowledgement")
|
||||
}
|
||||
_ => return Err(GatewayClientError::FatalKeyUpgradeFailure),
|
||||
}
|
||||
|
||||
// perform in memory swap and make a copy for updating storage
|
||||
let zeroizing_updated_key = updated_key.zeroizing_clone();
|
||||
self.shared_key = Some(Arc::new(updated_key.into()));
|
||||
|
||||
Ok(zeroizing_updated_key)
|
||||
}
|
||||
|
||||
async fn send_authenticate_request_and_handle_response(
|
||||
&mut self,
|
||||
msg: ClientControlRequest,
|
||||
@@ -606,17 +535,14 @@ impl<C, St> GatewayClient<C, St> {
|
||||
upgrade_mode,
|
||||
} => {
|
||||
if protocol_version.is_future_version() {
|
||||
// SAFETY: future version is always defined
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let version = protocol_version.unwrap();
|
||||
error!("the gateway insists on using v{version} protocol which is not supported by this client");
|
||||
error!("the gateway insists on using v{protocol_version} protocol which is not supported by this client");
|
||||
return Err(GatewayClientError::AuthenticationFailure);
|
||||
}
|
||||
self.authenticated = status;
|
||||
self.bandwidth
|
||||
.update_and_maybe_log(bandwidth_remaining, upgrade_mode);
|
||||
|
||||
self.negotiated_protocol = protocol_version;
|
||||
self.negotiated_protocol = Some(protocol_version);
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
if upgrade_mode {
|
||||
warn!("the system is currently undergoing an upgrade. some of its functionalities might be unstable")
|
||||
@@ -629,27 +555,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v1 authentication");
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let self_address = self
|
||||
.local_identity
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_legacy_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate_v2(
|
||||
&mut self,
|
||||
requested_protocol_version: GatewayProtocolVersion,
|
||||
@@ -670,30 +575,22 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
async fn authenticate(
|
||||
&mut self,
|
||||
supported_gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
requested_protocol_version: GatewayProtocolVersion,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
if supported_gateway_protocol.supports_authenticate_v2() {
|
||||
// use the highest possible protocol version the gateway has announced support for
|
||||
|
||||
// SAFETY: if announced protocol supports auth v2, it means it's properly set
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.authenticate_v2(supported_gateway_protocol.unwrap())
|
||||
.await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
// use the highest possible protocol version the gateway has announced support for
|
||||
self.authenticate_v2(requested_protocol_version).await
|
||||
}
|
||||
|
||||
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||
#[instrument(skip_all,
|
||||
fields(
|
||||
gateway = %self.gateway_identity,
|
||||
gateway_address = %self.gateway_address
|
||||
gateway_address = %self.gateway_addresses.primary
|
||||
)
|
||||
)]
|
||||
pub async fn perform_initial_authentication(
|
||||
@@ -704,15 +601,9 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// 1. check gateway's protocol version
|
||||
let gw_protocol = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => Some(protocol),
|
||||
Err(_) => {
|
||||
// if we failed to send the request, it means the gateway is running the old binary,
|
||||
// so it has reset our connection - we have to reconnect
|
||||
self.establish_connection().await?;
|
||||
None
|
||||
}
|
||||
};
|
||||
// if we failed to get this request resolved, it means the gateway is on an old version
|
||||
// that definitely does not support auth v2 or aes256gcm, so we bail
|
||||
let gw_protocol = self.get_gateway_protocol().await?;
|
||||
|
||||
debug!("supported gateway protocol: {gw_protocol:?}");
|
||||
|
||||
@@ -727,6 +618,16 @@ impl<C, St> GatewayClient<C, St> {
|
||||
if !supports_auth_v2 {
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2")
|
||||
}
|
||||
|
||||
// Dropping v1 support
|
||||
if !supports_auth_v2 || !supports_aes_gcm_siv {
|
||||
// we can't continue
|
||||
return Err(GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gw_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
if !supports_key_rotation_info {
|
||||
warn!("this gateway is on an old version that doesn't support key rotation packets")
|
||||
}
|
||||
@@ -736,7 +637,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
let gw_protocol = if gw_protocol.is_future_version() {
|
||||
warn!("we're running outdated software as gateway is announcing protocol {gw_protocol:?} whilst we're using {}. we're going to attempt to downgrade", GatewayProtocolVersion::CURRENT);
|
||||
Some(GatewayProtocolVersion::CURRENT)
|
||||
GatewayProtocolVersion::CURRENT
|
||||
} else {
|
||||
gw_protocol
|
||||
};
|
||||
@@ -746,7 +647,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
return if let Some(shared_key) = &self.shared_key {
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: shared_key.is_legacy() && supports_aes_gcm_siv,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailureWithPreexistingSharedKey)
|
||||
@@ -761,11 +661,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
|
||||
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailure)
|
||||
@@ -781,7 +678,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// so no upgrades are required
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1143,7 +1039,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate(self.negotiated_protocol).await?;
|
||||
if let Some(negotiated_protocol) = self.negotiated_protocol {
|
||||
self.authenticate(negotiated_protocol).await?;
|
||||
} else {
|
||||
// This should never happen, because it would mean we're not registered
|
||||
return Err(GatewayClientError::NotRegistered);
|
||||
}
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
@@ -1188,7 +1089,7 @@ pub struct InitOnly;
|
||||
impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
// for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic...
|
||||
pub fn new_init(
|
||||
gateway_listener: Url,
|
||||
gateway_listeners: GatewayListeners,
|
||||
gateway_identity: ed25519::PublicKey,
|
||||
local_identity: Arc<ed25519::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -1207,7 +1108,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
cfg: GatewayClientConfig::default().with_disabled_credentials_mode(true),
|
||||
authenticated: false,
|
||||
bandwidth: ClientBandwidth::new_empty(),
|
||||
gateway_address: gateway_listener.to_string(),
|
||||
gateway_addresses: gateway_listeners,
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
shared_key: None,
|
||||
@@ -1239,7 +1140,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
cfg: self.cfg,
|
||||
authenticated: self.authenticated,
|
||||
bandwidth: self.bandwidth,
|
||||
gateway_address: self.gateway_address,
|
||||
gateway_addresses: self.gateway_addresses,
|
||||
gateway_identity: self.gateway_identity,
|
||||
local_identity: self.local_identity,
|
||||
shared_key: self.shared_key,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::client::GatewayListeners;
|
||||
use crate::error::GatewayClientError;
|
||||
|
||||
use nym_http_api_client::HickoryDnsResolver;
|
||||
@@ -85,3 +87,35 @@ pub(crate) async fn connect_async(
|
||||
source: Box::new(error),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) async fn connect_async_with_fallback(
|
||||
endpoints: &GatewayListeners,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||
match connect_async(
|
||||
endpoints.primary.as_ref(),
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(inner) => Ok(inner),
|
||||
Err(e) => {
|
||||
if let Some(fallback) = &endpoints.fallback {
|
||||
tracing::warn!(
|
||||
"Main endpoint failed {} : {e}, trying fallback : {fallback}",
|
||||
endpoints.primary
|
||||
);
|
||||
connect_async(
|
||||
fallback.as_ref(),
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,9 @@ pub enum GatewayClientError {
|
||||
#[error("Client is not authenticated")]
|
||||
NotAuthenticated,
|
||||
|
||||
#[error("Client is not registered")]
|
||||
NotRegistered,
|
||||
|
||||
#[error("Client does not have enough bandwidth: estimated {0}, remaining: {1}")]
|
||||
NotEnoughBandwidth(i64, i64),
|
||||
|
||||
@@ -116,8 +119,8 @@ pub enum GatewayClientError {
|
||||
#[error("Failed to send mixnet message")]
|
||||
MixnetMsgSenderFailedToSend,
|
||||
|
||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
|
||||
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
|
||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway}")]
|
||||
IncompatibleProtocol { gateway: u8, current: u8 },
|
||||
|
||||
#[error(
|
||||
"The packet router hasn't been set - are you sure you started up the client correctly?"
|
||||
|
||||
@@ -7,9 +7,7 @@ use tracing::{error, warn};
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub use client::{config::GatewayClientConfig, GatewayClient, GatewayConfig};
|
||||
pub use nym_gateway_requests::shared_key::{
|
||||
LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey,
|
||||
};
|
||||
pub use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
PacketRouter,
|
||||
@@ -47,7 +45,7 @@ pub(crate) fn cleanup_socket_messages(
|
||||
|
||||
pub(crate) fn try_decrypt_binary_message(
|
||||
bin_msg: Vec<u8>,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
) -> Option<Vec<u8>> {
|
||||
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) {
|
||||
Ok(bin_response) => match bin_response {
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{cleanup_socket_messages, try_decrypt_binary_message};
|
||||
use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::{
|
||||
SendResponse, SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError,
|
||||
};
|
||||
@@ -66,7 +66,7 @@ pub(crate) struct PartiallyDelegatedHandle {
|
||||
|
||||
struct PartiallyDelegatedRouter {
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
|
||||
stream_return: SplitStreamSender,
|
||||
@@ -76,7 +76,7 @@ struct PartiallyDelegatedRouter {
|
||||
impl PartiallyDelegatedRouter {
|
||||
fn new(
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
stream_return: SplitStreamSender,
|
||||
stream_return_requester: oneshot::Receiver<()>,
|
||||
@@ -214,11 +214,6 @@ impl PartiallyDelegatedRouter {
|
||||
SensitiveServerResponse::RememberMeAck {} => {
|
||||
info!("received remember me acknowledgement");
|
||||
}
|
||||
SensitiveServerResponse::KeyUpgradeAck {} => {
|
||||
warn!(
|
||||
"received illegal key upgrade acknowledgement in an authenticated client"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
warn!("received unknown SensitiveServerResponse");
|
||||
}
|
||||
@@ -294,7 +289,7 @@ impl PartiallyDelegatedHandle {
|
||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||
conn: WsConn,
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
shutdown: ShutdownToken,
|
||||
) -> Self {
|
||||
|
||||
@@ -75,6 +75,7 @@ workspace = true
|
||||
features = ["json", "rustls-tls"]
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
cosmrs = { workspace = true, features = ["bip32"] }
|
||||
ts-rs = { workspace = true }
|
||||
|
||||
@@ -7,6 +7,7 @@ use cosmrs::{tx, AccountId, Coin, Denom};
|
||||
use nym_validator_client::http_client;
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
use nym_validator_client::signing::tx_signer::TxSigner;
|
||||
use nym_validator_client::signing::SignerData;
|
||||
|
||||
@@ -19,8 +20,8 @@ async fn main() {
|
||||
let validator = "https://rpc.sandbox.nymtech.net";
|
||||
let to_address: AccountId = "n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa".parse().unwrap();
|
||||
|
||||
let signer = DirectSecp256k1HdWallet::from_mnemonic(prefix, signer_mnemonic);
|
||||
let signer_address = signer.try_derive_accounts().unwrap()[0].address().clone();
|
||||
let signer = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, signer_mnemonic).unwrap();
|
||||
let signer_address = signer.signer_addresses()[0].clone();
|
||||
|
||||
// local 'client' ONLY signing messages
|
||||
let tx_signer = signer;
|
||||
@@ -57,9 +58,15 @@ async fn main() {
|
||||
100000u32,
|
||||
);
|
||||
|
||||
let tx_raw = tx_signer
|
||||
.sign_direct(&signer_address, vec![send_msg], fee, memo, signer_data)
|
||||
.unwrap();
|
||||
let tx_raw = TxSigner::sign_direct(
|
||||
&tx_signer,
|
||||
&signer_address,
|
||||
vec![send_msg],
|
||||
fee,
|
||||
memo,
|
||||
signer_data,
|
||||
)
|
||||
.unwrap();
|
||||
let tx_bytes = tx_raw.to_bytes().unwrap();
|
||||
|
||||
// compare balances from before and after the tx
|
||||
|
||||
@@ -5,8 +5,7 @@ use crate::nyxd::{self, NyxdClient};
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::{NoSigner, OfflineSigner};
|
||||
use crate::{
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
|
||||
ValidatorClientError,
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ValidatorClientError,
|
||||
};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -164,7 +163,7 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
|
||||
) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
|
||||
let rpc_client = http_client(config.nyxd_url.as_str())?;
|
||||
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
|
||||
|
||||
Ok(Self::new_signing_with_rpc_client(
|
||||
config, rpc_client, wallet,
|
||||
@@ -177,12 +176,13 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client<ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
#[allow(deprecated)]
|
||||
impl Client<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
pub fn new_reqwest_signing(
|
||||
config: Config,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> DirectSigningReqwestRpcValidatorClient {
|
||||
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
|
||||
@@ -203,9 +203,10 @@ impl Client<HttpRpcClient> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client<ReqwestRpcClient> {
|
||||
#[allow(deprecated)]
|
||||
impl Client<crate::ReqwestRpcClient> {
|
||||
pub fn new_reqwest_query(config: Config) -> QueryReqwestRpcValidatorClient {
|
||||
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
Self::new_with_rpc_client(config, rpc_client)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nym_api;
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
|
||||
pub use tendermint_rpc::error::Error as TendermintRpcError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -26,6 +27,12 @@ pub enum ValidatorClientError {
|
||||
|
||||
#[error("No validator API url has been provided")]
|
||||
NoAPIUrlAvailable,
|
||||
|
||||
#[error("failed to derive signing accounts: {source}")]
|
||||
AccountDerivationFailure {
|
||||
#[from]
|
||||
source: DirectSecp256k1HdWalletError,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<nym_api::error::NymAPIError> for ValidatorClientError {
|
||||
|
||||
@@ -12,6 +12,7 @@ pub mod rpc;
|
||||
pub mod signing;
|
||||
|
||||
pub use crate::error::ValidatorClientError;
|
||||
#[allow(deprecated)]
|
||||
pub use crate::rpc::reqwest::ReqwestRpcClient;
|
||||
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
pub use client::{Client, Config, EcashApiClient};
|
||||
@@ -38,9 +39,13 @@ pub type DirectSigningHttpRpcValidatorClient = Client<HttpRpcClient, DirectSecp2
|
||||
#[cfg(feature = "http-client")]
|
||||
pub type DirectSigningHttpRpcNyxdClient = nyxd::NyxdClient<HttpRpcClient, DirectSecp256k1HdWallet>;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub type QueryReqwestRpcValidatorClient = Client<ReqwestRpcClient>;
|
||||
#[allow(deprecated)]
|
||||
pub type QueryReqwestRpcNyxdClient = nyxd::NyxdClient<ReqwestRpcClient>;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub type DirectSigningReqwestRpcValidatorClient = Client<ReqwestRpcClient, DirectSecp256k1HdWallet>;
|
||||
#[allow(deprecated)]
|
||||
pub type DirectSigningReqwestRpcNyxdClient =
|
||||
nyxd::NyxdClient<ReqwestRpcClient, DirectSecp256k1HdWallet>;
|
||||
|
||||
@@ -178,7 +178,7 @@ where
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
|
||||
self.execute(signer_address, dkg_contract_address, &msg, fee, memo, funds)
|
||||
.await
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ where
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
|
||||
self.execute(
|
||||
signer_address,
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
group_contract_address,
|
||||
|
||||
+1
-1
@@ -667,7 +667,7 @@ where
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let memo = msg.default_memo();
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
mixnet_contract_address,
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
multisig_contract_address,
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
performance_contract_address,
|
||||
|
||||
+1
-1
@@ -375,7 +375,7 @@ where
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let memo = msg.name().to_string();
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
vesting_contract_address,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user