mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 06:38:32 -04:00
Allow passing private key using file.
Adds warning about passing key directly in CLI. On *nix systems, checks file permissions for warnings. Fixes #19
This commit is contained in:
parent
7545c7a3a8
commit
75d6a0a11c
2 changed files with 63 additions and 8 deletions
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::fs::read_to_string;
|
||||||
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -17,10 +18,13 @@ pub struct Config {
|
||||||
pub(crate) source_peer_ip: IpAddr,
|
pub(crate) source_peer_ip: IpAddr,
|
||||||
pub(crate) keepalive_seconds: Option<u16>,
|
pub(crate) keepalive_seconds: Option<u16>,
|
||||||
pub(crate) log: String,
|
pub(crate) log: String,
|
||||||
|
pub(crate) warnings: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn from_args() -> anyhow::Result<Self> {
|
pub fn from_args() -> anyhow::Result<Self> {
|
||||||
|
let mut warnings = vec![];
|
||||||
|
|
||||||
let matches = App::new("onetun")
|
let matches = App::new("onetun")
|
||||||
.author("Aram Peres <aram.peres@gmail.com>")
|
.author("Aram Peres <aram.peres@gmail.com>")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
@ -43,11 +47,17 @@ impl Config {
|
||||||
\tlocalhost:8080:peer.intranet:8081:TCP\
|
\tlocalhost:8080:peer.intranet:8081:TCP\
|
||||||
"),
|
"),
|
||||||
Arg::with_name("private-key")
|
Arg::with_name("private-key")
|
||||||
.required(true)
|
.required_unless("private-key-file")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.long("private-key")
|
.long("private-key")
|
||||||
.env("ONETUN_PRIVATE_KEY")
|
.env("ONETUN_PRIVATE_KEY")
|
||||||
.help("The private key of this peer. The corresponding public key should be registered in the Wireguard endpoint."),
|
.help("The private key of this peer. The corresponding public key should be registered in the Wireguard endpoint. \
|
||||||
|
You can also use '--private-key-file' to specify a file containing the key instead."),
|
||||||
|
Arg::with_name("private-key-file")
|
||||||
|
.takes_value(true)
|
||||||
|
.long("private-key-file")
|
||||||
|
.env("ONETUN_PRIVATE_KEY_FILE")
|
||||||
|
.help("The path to a file containing the private key of this peer. The corresponding public key should be registered in the Wireguard endpoint."),
|
||||||
Arg::with_name("endpoint-public-key")
|
Arg::with_name("endpoint-public-key")
|
||||||
.required(true)
|
.required(true)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
@ -110,11 +120,38 @@ impl Config {
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Read private key from file or CLI argument
|
||||||
|
let (group_readable, world_readable) = matches
|
||||||
|
.value_of("private-key-file")
|
||||||
|
.map(is_file_insecurely_readable)
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default();
|
||||||
|
if group_readable {
|
||||||
|
warnings.push("Private key file is group-readable. This is insecure.".into());
|
||||||
|
}
|
||||||
|
if world_readable {
|
||||||
|
warnings.push("Private key file is world-readable. This is insecure.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let private_key = if let Some(private_key_file) = matches.value_of("private-key-file") {
|
||||||
|
read_to_string(private_key_file)
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.with_context(|| "Failed to read private key file")
|
||||||
|
} else {
|
||||||
|
if std::env::var("ONETUN_PRIVATE_KEY").is_err() {
|
||||||
|
warnings.push("Private key was passed using CLI. This is insecure. \
|
||||||
|
Use \"--private-key-file <file containing private key>\", or the \"ONETUN_PRIVATE_KEY\" env variable instead.".into());
|
||||||
|
}
|
||||||
|
matches
|
||||||
|
.value_of("private-key")
|
||||||
|
.map(String::from)
|
||||||
|
.with_context(|| "Missing private key")
|
||||||
|
}?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
port_forwards,
|
port_forwards,
|
||||||
private_key: Arc::new(
|
private_key: Arc::new(
|
||||||
parse_private_key(matches.value_of("private-key"))
|
parse_private_key(&private_key).with_context(|| "Invalid private key")?,
|
||||||
.with_context(|| "Invalid private key")?,
|
|
||||||
),
|
),
|
||||||
endpoint_public_key: Arc::new(
|
endpoint_public_key: Arc::new(
|
||||||
parse_public_key(matches.value_of("endpoint-public-key"))
|
parse_public_key(matches.value_of("endpoint-public-key"))
|
||||||
|
@ -127,6 +164,7 @@ impl Config {
|
||||||
keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive"))
|
keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive"))
|
||||||
.with_context(|| "Invalid keep-alive value")?,
|
.with_context(|| "Invalid keep-alive value")?,
|
||||||
log: matches.value_of("log").unwrap_or_default().into(),
|
log: matches.value_of("log").unwrap_or_default().into(),
|
||||||
|
warnings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,11 +183,9 @@ fn parse_ip(s: Option<&str>) -> anyhow::Result<IpAddr> {
|
||||||
.with_context(|| "Invalid IP address")
|
.with_context(|| "Invalid IP address")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_private_key(s: Option<&str>) -> anyhow::Result<X25519SecretKey> {
|
fn parse_private_key(s: &str) -> anyhow::Result<X25519SecretKey> {
|
||||||
s.with_context(|| "Missing private key")?
|
s.parse::<X25519SecretKey>()
|
||||||
.parse::<X25519SecretKey>()
|
|
||||||
.map_err(|e| anyhow::anyhow!("{}", e))
|
.map_err(|e| anyhow::anyhow!("{}", e))
|
||||||
.with_context(|| "Invalid private key")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_public_key(s: Option<&str>) -> anyhow::Result<X25519PublicKey> {
|
fn parse_public_key(s: Option<&str>) -> anyhow::Result<X25519PublicKey> {
|
||||||
|
@ -173,6 +209,21 @@ fn parse_keep_alive(s: Option<&str>) -> anyhow::Result<Option<u16>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> {
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
let mode = File::open(&path).ok()?.metadata().ok()?.mode();
|
||||||
|
Some((mode & 0o40 > 0, mode & 0o4 > 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> {
|
||||||
|
// No good way to determine permissions on non-Unix target
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct PortForwardConfig {
|
pub struct PortForwardConfig {
|
||||||
/// The source IP and port where the local server will run.
|
/// The source IP and port where the local server will run.
|
||||||
|
|
|
@ -22,6 +22,10 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let config = Config::from_args().with_context(|| "Failed to read config")?;
|
let config = Config::from_args().with_context(|| "Failed to read config")?;
|
||||||
init_logger(&config)?;
|
init_logger(&config)?;
|
||||||
|
|
||||||
|
for warning in &config.warnings {
|
||||||
|
warn!("{}", warning);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the port pool for each protocol
|
// Initialize the port pool for each protocol
|
||||||
let tcp_port_pool = TcpPortPool::new();
|
let tcp_port_pool = TcpPortPool::new();
|
||||||
let udp_port_pool = UdpPortPool::new();
|
let udp_port_pool = UdpPortPool::new();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue