[WIP] feat(rpc): implement JSON-RPC authentication#1105
Conversation
3aec2bc to
93cfa56
Compare
|
You’re on the right track. In this case, you can create an Auth object to store the authentication information, because at the RPC layer it doesn’t need to know whether the user set a username and password, is using no authentication, or is using the default authenticated mode. The only thing it needs to do is, for each request, take the provided information and pass it to Auth, and then Auth decides whether the request is authorized or not. So this Auth object should have three initialization methods: one for when authentication is disabled, one for when the user provides a username and password, and another for the default mode, where each initialization generates a new .cookie file. After that, it should have a method like valid_login that receives two |
ea4cc4a to
9821ca6
Compare
|
Also needs rebase pinning |
|
@luisschwab just noticed ur recent PR merged...will update this accordingly....also looking into the bdk example shared |
a630b7a to
c8872c5
Compare
Write <datadir>/.cookie on startup as a single line __cookie__:<64-char-hex-token>, no trailing newline. Token is 32 bytes from rand::rng(), lowercase hex-encoded. File mode 0600 on Unix via OpenOptionsExt; atomic publish via tmp + rename. Refs: getfloresta#651
Track cookie ownership via a cookie_generated OnceLock on Florestad: set after generate_cookie succeeds at startup, checked at wait_shutdown so we only remove a cookie this process wrote. delete_cookie treats NotFound as success, so shutdown stays idempotent across crashes and double-stops. Refs: getfloresta#651
Add parse_basic_auth_header and BasicAuthHeaderError. The parser matches Bitcoin Core's wire semantics: case-sensitive "Basic " prefix, whitespace-trim around the base64 payload, and split on the first ':' so passwords may contain colons. An axum middleware reads the Authorization header on each request, parses it, and logs the parsed username at debug level. Missing, non-ASCII, or malformed headers are also logged at debug. The middleware always passes the request through; this layer does not reject anything. Refs: getfloresta#651
ddefb98 to
31d8e95
Compare
The JSON-RPC middleware now gates every request: missing, malformed,
or non-matching Authorization: Basic headers return HTTP 401 with
WWW-Authenticate: Basic realm="jsonrpc" per RFC 7235.
generate_cookie returns the cookie line again so Florestad can wrap
it in Arc<Credentials::Cookie> and pass it through to the middleware
state. Credentials::matches compares format!("{user}:{pass}") against
the stored line via a hand-rolled constant_time_eq that runs the full
length regardless of where the first mismatch lies.
floresta-cli reads <datadir>/[<net>/].cookie by default; --rpc-user +
--rpc-password override; --rpc-cookie-file picks a non-default path.
The Python test framework reads the cookie after the RPC socket opens.
Refs: getfloresta#651
31d8e95 to
b56f667
Compare
Add rpc_user/rpc_password to florestad::Config, exposed via --rpc-user/--rpc-password CLI flags and an [rpc] section in the TOML config file. CLI values take precedence over file values. Rename Credentials to Auth and add an Auth::UserPass variant that stores the user:pass pair as a single pre-formatted line, sharing the constant-time matches() path with Auth::Cookie. When rpc_password is set, Florestad::start uses Auth::UserPass and skips cookie generation, mirroring Bitcoin Core's mutual exclusion between cookie auth and configured passwords. The cookie_generated flag stays unset so wait_shutdown also skips deletion. Refs: getfloresta#651
3903ad8 to
f7da517
Compare
Replace Auth::UserPass with Auth::Hashed(Vec<RpcAuth>) and hash the configured -rpcuser/-rpcpassword pair at startup. Florestad keeps only (user, salt_hex, hash_hex) in memory; the plaintext password is discarded after the HMAC. RpcAuth::from_password rolls a 16-byte salt, hex-encodes it (32 ASCII chars), and HMAC-SHA256s the password. RpcAuth::verify recomputes the HMAC on each request and constant-time-compares both the username and the digest. The HMAC key is the salt's hex string as ASCII bytes (NOT the 16 decoded bytes), matching Bitcoin Core's share/rpcauth/rpcauth.py. A pinned test vector asserts the digest matches rpcauth.py for a known (salt, password) pair. Refs: getfloresta#651
|
@luisschwab @moisesPompilio ...addressed your suggestions on here f7da517#diff-20e4d7a573928231cbeb3947c1be0523276ed18d93056fc9e6265243f3637785R179-R187 |
Add RpcAuth::parse for the <user>:<salt>$<hash> line format from Bitcoin Core's share/rpcauth/rpcauth.py. Collapse Auth into one Vec<RpcAuth> matching Core's g_rpcauth model. Cookie auth is now another hashed entry: generate_cookie returns (user, token) and the cookie's token is hashed and pushed alongside any -rpcuser/-rpcpassword and -rpcauth entries. Config gains rpc_auth: Vec<String> via --rpc-auth (repeatable) and an auth = [...] key under [rpc] in the TOML file. Refs: getfloresta#651
On credentials mismatch the middleware logs at warn level with the peer's IP and sleeps 250 ms before returning the 401. Missing or malformed headers still get an instant 401. Peer address is extracted via axum's ConnectInfo, threaded through with into_make_service_with_connect_info::<SocketAddr>() on the router. Refs: getfloresta#651
On startup the daemon collects every configured credential into a single
Vec<RpcAuth>: a cookie entry (when no--rpc-passwordis set), a-rpcuser/-rpcpasswordentry, and any-rpcauthentries. All entries are stored as(user, salt_hex, hash_hex); plaintext passwords are HMAC-SHA256'd and discarded.The middleware iterates the vector at request time and returns 200 on first match, 401 with
WWW-Authenticate: Basic realm="jsonrpc"otherwise. The cookie file rotates per restart and is removed on graceful shutdown.What the in-memory vector looks like for the three configurations:
entriesafter startupflorestad(default)[RpcAuth { user: "__cookie__", salt, hash }]florestad --rpc-user=alice --rpc-password=hunter2[RpcAuth { user: "alice", salt, hash }]florestad --rpc-auth=bob:s$h --rpc-auth=carol:s$h[RpcAuth { user: "__cookie__", ... }, RpcAuth { user: "bob", ... }, RpcAuth { user: "carol", ... }]floresta-cli reads the cookie automatically;
--rpc-userand--rpc-passwordoverride;--rpc-cookie-filepicks a non-default path.Closes #651.