-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(rpc): Cookie auth system for the RPC endpoint #8900
Open
oxarbitrage
wants to merge
13
commits into
main
Choose a base branch
from
auth-rpc-endpoint
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+222
−7
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
8b49c18
add a cookie auth system for the rpc endpoint
oxarbitrage 045048e
fix rand import
oxarbitrage 212d517
fixes based on cookie method research
oxarbitrage e91b139
add and use `cookie_dir` config, rpc client changes
oxarbitrage efdbd2a
add missing dependency
oxarbitrage 7a7c14a
Merge remote-tracking branch 'origin/main' into auth-rpc-endpoint
oxarbitrage f02159e
add a enable_cookie auth option to config and use it in all tests
oxarbitrage 963c1d2
get rid of the unauthenticated method
oxarbitrage 2f5c27c
Merge remote-tracking branch 'origin/main' into auth-rpc-endpoint
oxarbitrage dc4f388
change config in qa python tests to run unauthenticated
oxarbitrage d48d9d4
change return types in cookie methods
oxarbitrage 7708294
change comment
oxarbitrage ad54729
Merge branch 'main' into auth-rpc-endpoint
upbqdn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
//! Cookie-based authentication for the RPC server. | ||
|
||
use base64::{engine::general_purpose::URL_SAFE, Engine as _}; | ||
use color_eyre::Result; | ||
use rand::RngCore; | ||
|
||
use std::{ | ||
fs::{remove_file, File}, | ||
io::{Read, Write}, | ||
path::PathBuf, | ||
}; | ||
|
||
/// The user field in the cookie (arbitrary, only for recognizability in debugging/logging purposes) | ||
pub const COOKIEAUTH_USER: &str = "__cookie__"; | ||
/// Default name for auth cookie file */ | ||
pub const COOKIEAUTH_FILE: &str = ".cookie"; | ||
|
||
/// Generate a new auth cookie and store it in the given `cookie_dir`. | ||
pub fn generate(cookie_dir: PathBuf) -> Result<()> { | ||
let mut data = [0u8; 32]; | ||
rand::thread_rng().fill_bytes(&mut data); | ||
let encoded_password = URL_SAFE.encode(data); | ||
let cookie_content = format!("{}:{}", COOKIEAUTH_USER, encoded_password); | ||
|
||
let mut file = File::create(cookie_dir.join(COOKIEAUTH_FILE))?; | ||
file.write_all(cookie_content.as_bytes())?; | ||
|
||
tracing::info!("RPC auth cookie generated successfully"); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Get the encoded password from the auth cookie. | ||
pub fn get(cookie_dir: PathBuf) -> Result<String> { | ||
let mut file = File::open(cookie_dir.join(COOKIEAUTH_FILE))?; | ||
let mut contents = String::new(); | ||
file.read_to_string(&mut contents)?; | ||
|
||
let parts: Vec<&str> = contents.split(":").collect(); | ||
Ok(parts[1].to_string()) | ||
} | ||
|
||
/// Delete the auth cookie. | ||
pub fn delete() -> Result<()> { | ||
remove_file(COOKIEAUTH_FILE)?; | ||
tracing::info!("RPC auth cookie deleted successfully"); | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,15 @@ | |
//! | ||
//! These fixes are applied at the HTTP level, before the RPC request is parsed. | ||
|
||
use base64::{engine::general_purpose::URL_SAFE, Engine as _}; | ||
use futures::TryStreamExt; | ||
use jsonrpc_http_server::{ | ||
hyper::{body::Bytes, header, Body, Request}, | ||
RequestMiddleware, RequestMiddlewareAction, | ||
}; | ||
|
||
use crate::server::cookie; | ||
|
||
/// HTTP [`RequestMiddleware`] with compatibility workarounds. | ||
/// | ||
/// This middleware makes the following changes to HTTP requests: | ||
|
@@ -34,13 +37,30 @@ use jsonrpc_http_server::{ | |
/// Any user-specified data in RPC requests is hex or base58check encoded. | ||
/// We assume lightwalletd validates data encodings before sending it on to Zebra. | ||
/// So any fixes Zebra performs won't change user-specified data. | ||
#[derive(Copy, Clone, Debug)] | ||
pub struct FixHttpRequestMiddleware; | ||
#[derive(Clone, Debug)] | ||
pub struct FixHttpRequestMiddleware(pub crate::config::Config); | ||
|
||
impl RequestMiddleware for FixHttpRequestMiddleware { | ||
fn on_request(&self, mut request: Request<Body>) -> RequestMiddlewareAction { | ||
tracing::trace!(?request, "original HTTP request"); | ||
|
||
// Check if the request is authenticated | ||
if !self.check_credentials(request.headers_mut()) { | ||
let error = jsonrpc_core::Error { | ||
code: jsonrpc_core::ErrorCode::ServerError(401), | ||
message: "unauthenticated method".to_string(), | ||
data: None, | ||
}; | ||
return jsonrpc_http_server::Response { | ||
code: jsonrpc_http_server::hyper::StatusCode::from_u16(401) | ||
.expect("hard-coded status code should be valid"), | ||
content_type: header::HeaderValue::from_static("application/json; charset=utf-8"), | ||
content: serde_json::to_string(&jsonrpc_core::Response::from(error, None)) | ||
.expect("hard-coded result should serialize"), | ||
} | ||
.into(); | ||
} | ||
|
||
// Fix the request headers if needed and we can do so. | ||
FixHttpRequestMiddleware::insert_or_replace_content_type_header(request.headers_mut()); | ||
|
||
|
@@ -141,4 +161,30 @@ impl FixHttpRequestMiddleware { | |
); | ||
} | ||
} | ||
|
||
/// Check if the request is authenticated. | ||
pub fn check_credentials(&self, headers: &header::HeaderMap) -> bool { | ||
if !self.0.enable_cookie_auth { | ||
return true; | ||
} | ||
headers | ||
.get(header::AUTHORIZATION) | ||
.and_then(|auth_header| auth_header.to_str().ok()) | ||
.and_then(|auth| auth.split_whitespace().nth(1)) | ||
.and_then(|token| URL_SAFE.decode(token).ok()) | ||
.and_then(|decoded| String::from_utf8(decoded).ok()) | ||
.and_then(|decoded_str| { | ||
decoded_str | ||
.split(':') | ||
.nth(1) | ||
.map(|password| password.to_string()) | ||
}) | ||
.map_or(false, |password| { | ||
if let Ok(cookie_password) = cookie::get(self.0.cookie_dir.clone()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're still reading the cookie from the disk on each RPC request. |
||
cookie_password == password | ||
} else { | ||
false | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Default configuration for zebrad. | ||
# | ||
# This file can be used as a skeleton for custom configs. | ||
# | ||
# Unspecified fields use default values. Optional fields are Some(field) if the | ||
# field is present and None if it is absent. | ||
# | ||
# This file is generated as an example using zebrad's current defaults. | ||
# You should set only the config options you want to keep, and delete the rest. | ||
# Only a subset of fields are present in the skeleton, since optional values | ||
# whose default is None are omitted. | ||
# | ||
# The config format (including a complete list of sections and fields) is | ||
# documented here: | ||
# https://docs.rs/zebrad/latest/zebrad/config/struct.ZebradConfig.html | ||
# | ||
# zebrad attempts to load configs in the following order: | ||
# | ||
# 1. The -c flag on the command line, e.g., `zebrad -c myconfig.toml start`; | ||
# 2. The file `zebrad.toml` in the users's preference directory (platform-dependent); | ||
# 3. The default config. | ||
# | ||
# The user's preference directory and the default path to the `zebrad` config are platform dependent, | ||
# based on `dirs::preference_dir`, see https://docs.rs/dirs/latest/dirs/fn.preference_dir.html : | ||
# | ||
# | Platform | Value | Example | | ||
# | -------- | ------------------------------------- | ---------------------------------------------- | | ||
# | Linux | `$XDG_CONFIG_HOME` or `$HOME/.config` | `/home/alice/.config/zebrad.toml` | | ||
# | macOS | `$HOME/Library/Preferences` | `/Users/Alice/Library/Preferences/zebrad.toml` | | ||
# | Windows | `{FOLDERID_RoamingAppData}` | `C:\Users\Alice\AppData\Local\zebrad.toml` | | ||
|
||
[consensus] | ||
checkpoint_sync = true | ||
|
||
[mempool] | ||
eviction_memory_time = "1h" | ||
tx_cost_limit = 80000000 | ||
|
||
[metrics] | ||
|
||
[mining] | ||
debug_like_zcashd = true | ||
|
||
[network] | ||
cache_dir = true | ||
crawl_new_peer_interval = "1m 1s" | ||
initial_mainnet_peers = [ | ||
"dnsseed.z.cash:8233", | ||
"dnsseed.str4d.xyz:8233", | ||
"mainnet.seeder.zfnd.org:8233", | ||
"mainnet.is.yolo.money:8233", | ||
] | ||
initial_testnet_peers = [ | ||
"dnsseed.testnet.z.cash:18233", | ||
"testnet.seeder.zfnd.org:18233", | ||
"testnet.is.yolo.money:18233", | ||
] | ||
listen_addr = "0.0.0.0:8233" | ||
max_connections_per_ip = 1 | ||
network = "Mainnet" | ||
peerset_initial_target_size = 25 | ||
|
||
[rpc] | ||
cookie_dir = "cache_dir" | ||
debug_force_finished_sync = false | ||
enable_cookie_auth = true | ||
parallel_cpu_threads = 0 | ||
|
||
[state] | ||
cache_dir = "cache_dir" | ||
delete_old_database = true | ||
ephemeral = false | ||
|
||
[sync] | ||
checkpoint_verify_concurrency_limit = 1000 | ||
download_concurrency_limit = 50 | ||
full_verify_concurrency_limit = 20 | ||
parallel_cpu_threads = 0 | ||
|
||
[tracing] | ||
buffer_limit = 128000 | ||
force_use_color = false | ||
use_color = true | ||
use_journald = false |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the removal works because we're using only the file name without the path to it.