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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 0 additions & 54 deletions Cargo.lock

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

3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ clap = { version = "4.5.45", features = ["derive"] }
lnurl-rs = { version = "0.9.0", default-features = false, features = ["ureq"] }
once_cell = "1.20.2"
bitcoin = "0.32.5"
rpassword = "7.3.1"
argon2 = "0.5"
secrecy = "0.10.0"
dirs = "6.0.0"
clearscreen = "4.0.1"
tonic = "0.14.2"
Expand Down
6 changes: 2 additions & 4 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,12 @@ To build and run the Docker container using Docker Compose, follow these steps:
make docker-up
```

Or pass it inline:
Or set the variable on one line before `make docker-up`:
```sh
MOSTRO_RELAY_LOCAL_PORT=7000 make docker-up
```

5. **Note:** Database encryption has been removed. The `MOSTRO_DB_PASSWORD` environment variable (if set in `compose.yml`) is no longer used for the database; you can omit it. For more details about environment variables, see [ENV_VARIABLES.md](ENV_VARIABLES.md).

6. Run the docker compose file:
5. Run the docker compose file:

```sh
make docker-up
Expand Down
4 changes: 0 additions & 4 deletions docker/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ services:
build:
context: ..
dockerfile: docker/Dockerfile
environment:
MOSTRO_DB_PASSWORD: ${MOSTRO_DB_PASSWORD-}
volumes:
- ./config:/config # settings.toml and mostro.db
platform: linux/amd64
Expand All @@ -15,8 +13,6 @@ services:
build:
context: ..
dockerfile: docker/dockerfile-startos
environment:
MOSTRO_DB_PASSWORD: ${MOSTRO_DB_PASSWORD-}
platform: linux/amd64
networks:
- default
Expand Down
23 changes: 20 additions & 3 deletions docs/RPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,67 +29,84 @@ port = 50051
The RPC interface supports the following admin operations:

### 1. Cancel Order

Cancel an order as an admin.

**Request:**

- `order_id`: UUID of the order to cancel
- `request_id`: Optional request identifier

**Response:**

- `success`: Boolean indicating operation success
- `error_message`: Optional error message if operation failed

### 2. Settle Order

Settle a disputed order as an admin.

**Request:**

- `order_id`: UUID of the order to settle
- `request_id`: Optional request identifier

**Response:**

- `success`: Boolean indicating operation success
- `error_message`: Optional error message if operation failed

### 3. Add Solver

Add a new dispute solver.

**Request:**

- `solver_pubkey`: Public key of the solver to add (in bech32 format)
- `request_id`: Optional request identifier

**Response:**

- `success`: Boolean indicating operation success
- `error_message`: Optional error message if operation failed

### 4. Take Dispute

Take a dispute for resolution.

**Request:**

- `dispute_id`: UUID of the dispute to take
- `request_id`: Optional request identifier

**Response:**

- `success`: Boolean indicating operation success
- `error_message`: Optional error message if operation failed

### 5. Validate Database Password
Kept for backward compatibility. Database encryption has been removed; this RPC always succeeds and does not validate a password.

Kept for backward compatibility with older clients. The SQLite database is **not** encrypted and this RPC does **not** validate any password; it always succeeds.

**Request:**
- `password`: Ignored
- `request_id`: Optional request identifier

- `password`: Ignored (kept in the protobuf for compatibility only)

**Response:**

- `success`: Always `true`
- `error_message`: Always `None`

### 6. Get Version

Retrieve the Mostro daemon version.

**Request:**

- No parameters required

**Response:**

- `version`: String containing the daemon version (from CARGO_PKG_VERSION)

## Protocol Details
Expand Down
102 changes: 45 additions & 57 deletions docs/RPC_RATE_LIMITING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,78 @@

## Overview

The `ValidateDbPassword` RPC endpoint is protected against brute-force attacks
with an in-memory rate limiter that tracks failed attempts per client IP.
The gRPC method **`ValidateDbPassword`** (protobuf RPC) is a **backward-compatibility** stub: the SQLite database is **not** encrypted, the request **`password`** field is **ignored**, and the response is always success after a per-IP gate.

## Problem
The implementation is **`validate_db_password`** in `src/rpc/service.rs`. It:

The `ValidateDbPassword` endpoint is kept for backward compatibility (database
encryption was removed, so it always succeeds). The rate limiter remains to
throttle abuse of this endpoint.
1. Resolves the client address and runs **`check_rate_limit`** on the shared in-memory **`RateLimiter`** (`src/rpc/rate_limiter.rs`).
2. Drops **`password`** on the floor (`let _ = req.password;`).
3. Calls **`record_success`** on the limiter for that IP (clears any tracked state for that key).
4. Returns **`ValidateDbPasswordResponse`** with `success: true`.

See [Issue #569](https://github.com/MostroP2P/mostro/issues/569) for full details.
**Important:** **`validate_db_password` does not call `record_failure`.** So exponential backoff and lockout described below are **generic `RateLimiter` capabilities** (used by unit tests and available if another call site ever records failures). They are **not** driven by repeated **`ValidateDbPassword`** calls with “wrong passwords,” because passwords are not validated.

## Implementation
See [Issue #569](https://github.com/MostroP2P/mostro/issues/569) for background.

### Rate Limiter (`src/rpc/rate_limiter.rs`)
## `ValidateDbPassword` ↔ code map

A lightweight, in-memory rate limiter keyed by client IP address. No external
dependencies required — uses only `tokio::sync::Mutex` and `std::collections::HashMap`.
| Concept | Where |
|--------|--------|
| RPC name | `ValidateDbPassword` in `proto/admin.proto` |
| Handler | `validate_db_password` in `src/rpc/service.rs` |
| Limiter | `password_rate_limiter: Arc<RateLimiter>` on `AdminServiceImpl` |
| Success path | `record_success(&remote_addr)` after ignoring `password` |

**Behavior:**
## Generic `RateLimiter` behavior (`src/rpc/rate_limiter.rs`)

| Failed Attempts | Response |
|----------------|----------|
The in-memory limiter is keyed by client IP. It exposes **`check_rate_limit`**, **`record_failure`**, and **`record_success`**.

**When `record_failure` is used** (e.g. in unit tests, or a hypothetical future handler), the limiter can apply exponential backoff and lockout:

| Failed attempts (`record_failure`) | Effect |
|-----------------------------------|--------|
| 1st | Immediate + 1s delay |
| 2nd | Immediate + 2s delay |
| 3rd | Immediate + 4s delay |
| 4th | Immediate + 8s delay |
| 5th+ | **Locked out for 5 minutes** |

After a successful validation, the client's failure state is reset.

### Integration (`src/rpc/service.rs`)

The `validate_db_password` method now:

1. Extracts the client's remote address from the gRPC request
2. Checks the rate limiter — returns `RESOURCE_EXHAUSTED` if locked out
3. Does not validate a password (database encryption was removed); always succeeds
4. On success: resets the client's failure state

### Audit Logging

All attempts are logged via `tracing`:

- **Rate-limited requests:** `WARN` with client IP
- **Failed attempts:** `WARN` with client IP and attempt count
- **Lockouts:** `WARN` with client IP and lockout duration

### Security Layers
After **`record_success`**, that IP’s failure state is cleared (see `record_success` in `rate_limiter.rs`).

This implementation addresses the issue's suggestions:
**Not exercised by `ValidateDbPassword` today:** the **`validate_db_password`** handler never invokes **`record_failure`**, so clients only exercising this RPC do not accumulate “failed attempts” through wrong passwords.

| Suggestion | Status | Notes |
|-----------|--------|-------|
| Rate limiting | ✅ | Per-IP tracking with exponential backoff |
| Exponential backoff | ✅ | 1s → 2s → 4s → 8s → lockout |
| Lockout | ✅ | 5-minute lockout after 5 failures |
| Audit logging | ✅ | All attempts logged via tracing |
| Localhost-only | ℹ️ | Default config already binds to `127.0.0.1` |
| Auth requirement | ℹ️ | Out of scope — would require session/API key infra |

### Constants

Configurable via constants in `src/rpc/rate_limiter.rs`:
### Constants (`src/rpc/rate_limiter.rs`)

```rust
const MAX_ATTEMPTS: u32 = 5;
const LOCKOUT_DURATION: Duration = Duration::from_secs(300); // 5 minutes
const BASE_DELAY_MS: u64 = 1000; // 1 second
```

### Thread Safety
### Thread safety

The rate limiter uses `tokio::sync::Mutex` for async-safe access. The lock is
dropped before applying the exponential backoff sleep to avoid holding it during
the delay.
The limiter uses `tokio::sync::Mutex`. The lock is dropped before the exponential backoff sleep inside **`record_failure`** so the mutex is not held across the delay.

## Testing
## Audit logging

- **`validate_db_password`** logs receipt of the RPC at **INFO** (client IP).
- **`RateLimiter`** may emit **WARN** for backoff/lockout when **`check_rate_limit`** denies an IP that already has failure state, or when **`record_failure`** runs — paths that matter for **unit tests** and for **generic** use of the limiter, not for password validation on **`ValidateDbPassword`**.

Unit tests in `src/rpc/rate_limiter.rs` verify:
## Security layers (historical issue checklist)

How the original issue’s ideas map to the codebase today:

| Suggestion | Notes |
|-----------|--------|
| Per-IP rate limiting | **`check_rate_limit`** runs before the handler body. |
| Exponential backoff / lockout | Implemented inside **`RateLimiter`**; **not** triggered by **`ValidateDbPassword`** (no **`record_failure`**). |
| Audit logging | **tracing** in service + limiter. |
| Localhost-only | Default RPC bind **`127.0.0.1`** (see `settings.toml` / `docs/RPC.md`). |
| Strong auth | Out of scope for this stub; would need API keys or similar. |

## Testing

- First attempt is always allowed
- Lockout triggers after `MAX_ATTEMPTS` failures
- Success resets the failure state
- Different IPs are tracked independently
Unit tests in **`src/rpc/rate_limiter.rs`** exercise **`record_failure`**, lockout, **`record_success`**, and eviction — they document **limiter** behavior, not password checking.

## Related

Expand Down
Loading
Loading