Reading password from environment variable or TTY

- servers creating from command line options would use TTY,
SS_SERVER_PASSWORD and SS_SERVER_${SERVER_ADDR}_PASSWORD environment
variables
- servers in configuration file "password" would allow ${VAR_NAME} to
read from environment variable VAR_NAME

fixes #419
This commit is contained in:
zonyitoo
2021-11-25 17:19:16 +08:00
parent aaac00f208
commit 74e8a4f8f5
9 changed files with 118 additions and 6 deletions

11
Cargo.lock generated
View File

@@ -1381,6 +1381,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "rpassword"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "rpmalloc"
version = "0.2.2"
@@ -1673,6 +1683,7 @@ dependencies = [
"log4rs",
"mimalloc",
"qrcode",
"rpassword",
"rpmalloc",
"shadowsocks-service",
"snmalloc-rs",

View File

@@ -130,6 +130,7 @@ exitcode = "1"
build-time = "0.1"
directories = "4.0"
xdg = "2.4"
rpassword = "5.0.1"
futures = "0.3"
tokio = { version = "1", features = ["rt", "signal"] }

View File

@@ -522,6 +522,14 @@ Example configuration:
// The higher weight, the server may rank higher.
"tcp_weight": 1.0,
"udp_weight": 1.0,
},
{
// Same key as basic format "server" and "server_port"
"server": "0.0.0.0",
"server_port": 8388,
"method": "chacha20-ietf-poly1305",
// Read the actual password from environment variable PASSWORD_FROM_ENV
"password": "${PASSWORD_FROM_ENV}"
}
],
@@ -583,6 +591,8 @@ Example configuration:
- `SS_LOG_VERBOSE_LEVEL`: Logging level for binaries (`sslocal`, `ssserver` and `ssmanager`). It is valid only when command line argument `-v` is not applied. Example: `SS_LOG_VERBOSE_LEVEL=1`
- `SS_LOG_WITHOUT_TIME`: Logging format for binaries (`sslocal`, `ssserver` and `ssmanager`). It is valid only when command line argument `--log-without-time` is not applied. Example `SS_LOG_WITHOUT_TIME=1`
- `SS_SERVER_PASSWORD`: A default password for servers that created from command line argument (`--server-addr`)
- `SS_SERVER_${SERVER_ADDR}_PASSWORD`: A default password for server with address `$SERVER_ADDR` that created from command line argument (`--server-addr`)
## Supported Ciphers

View File

@@ -1,3 +1,5 @@
//! Common configuration utilities
use directories::ProjectDirs;
use std::path::{Path, PathBuf};

View File

@@ -1,13 +1,14 @@
//! Shadowsocks service command line utilities
pub mod allocator;
pub mod config;
#[cfg(unix)]
pub mod daemonize;
#[cfg(feature = "logging")]
pub mod logging;
pub mod monitor;
pub mod password;
pub mod validator;
pub mod config;
pub const EXIT_CODE_SERVER_EXIT_UNEXPECTEDLY: i32 = exitcode::SOFTWARE;
pub const EXIT_CODE_SERVER_ABORTED: i32 = exitcode::SOFTWARE;

33
bin/common/password.rs Normal file
View File

@@ -0,0 +1,33 @@
//! Common password utilities
use std::{env, io};
use log::debug;
/// Read server's password from environment variable or TTY
pub fn read_server_password(server_name: &str) -> io::Result<String> {
// specific SS_SERVER_${server_name}_PASSWORD
let key = format!("SS_SERVER_{}_PASSWORD", server_name);
if let Ok(pwd) = env::var(&key) {
debug!("got server {} password from environment variable {}", server_name, key);
return Ok(pwd);
}
// common SS_SERVER_PASSWORD
if let Ok(pwd) = env::var("SS_SERVER_PASSWORD") {
debug!(
"got server {} password from environment variable SS_SERVER_PASSWORD",
server_name
);
return Ok(pwd);
}
// read from TTY
let tty_prompt = format!("({}) Password: ", server_name);
if let Ok(pwd) = rpassword::read_password_from_tty(Some(&tty_prompt)) {
debug!("got server {} password from tty prompt", server_name);
return Ok(pwd);
}
Err(io::Error::new(io::ErrorKind::Other, "no server password found"))
}

View File

@@ -227,7 +227,19 @@ fn main() {
};
if let Some(svr_addr) = matches.value_of("SERVER_ADDR") {
let password = matches.value_of("PASSWORD").expect("password");
let password = match matches.value_of("PASSWORD") {
Some(pwd) => pwd.to_owned(),
None => {
// NOTE: svr_addr should have been checked by common::validator
match common::password::read_server_password(svr_addr) {
Ok(pwd) => pwd,
Err(..) => {
panic!("missing server's password");
}
}
}
};
let method = matches
.value_of("ENCRYPT_METHOD")
.expect("encrypt-method")
@@ -240,7 +252,7 @@ fn main() {
.map(|t| t.parse::<u64>().expect("timeout"))
.map(Duration::from_secs);
let mut sc = ServerConfig::new(svr_addr, password.to_owned(), method);
let mut sc = ServerConfig::new(svr_addr, password, method);
if let Some(timeout) = timeout {
sc.set_timeout(timeout);
}

View File

@@ -151,7 +151,19 @@ fn main() {
};
if let Some(svr_addr) = matches.value_of("SERVER_ADDR") {
let password = matches.value_of("PASSWORD").expect("password");
let password = match matches.value_of("PASSWORD") {
Some(pwd) => pwd.to_owned(),
None => {
// NOTE: svr_addr should have been checked by common::validator
match common::password::read_server_password(svr_addr) {
Ok(pwd) => pwd,
Err(..) => {
panic!("missing server's password");
}
}
}
};
let method = matches
.value_of("ENCRYPT_METHOD")
.expect("encrypt-method")

View File

@@ -42,8 +42,10 @@
//! These defined server will be used with a load balancing algorithm.
use std::{
borrow::Cow,
convert::{From, Infallible},
default::Default,
env,
fmt::{self, Debug, Display, Formatter},
fs::OpenOptions,
io::Read,
@@ -58,6 +60,7 @@ use std::{
use cfg_if::cfg_if;
#[cfg(feature = "local-tun")]
use ipnet::IpNet;
use log::warn;
use serde::{Deserialize, Serialize};
#[cfg(any(feature = "local-tunnel", feature = "local-dns"))]
use shadowsocks::relay::socks5::Address;
@@ -1392,7 +1395,10 @@ impl Config {
}
};
let mut nsvr = ServerConfig::new(addr, pwd, method);
// Only "password" support getting from environment variable.
let password = get_variable_field_value(&pwd);
let mut nsvr = ServerConfig::new(addr, password, method);
nsvr.set_mode(global_mode);
if let Some(ref p) = config.plugin {
@@ -1459,7 +1465,10 @@ impl Config {
}
};
let mut nsvr = ServerConfig::new(addr, svr.password, method);
// Only "password" support getting from environment variable.
let password = get_variable_field_value(&svr.password);
let mut nsvr = ServerConfig::new(addr, password, method);
match svr.mode {
Some(mode) => match mode.parse::<Mode>() {
@@ -2216,3 +2225,24 @@ impl fmt::Display for Config {
write!(f, "{}", json5::to_string(&jconf).unwrap())
}
}
fn get_variable_field_value(value: &str) -> Cow<'_, str> {
if let Some(left_over) = value.strip_prefix("${") {
if let Some(var_name) = left_over.strip_suffix("}") {
return match env::var(var_name) {
Ok(value) => value.into(),
Err(err) => {
warn!(
"couldn't read password from environemnt variable {}, error: {}",
var_name, err
);
// NOTE: Just like shell, if environment variable not found, then the value of it is an empty string.
"".into()
}
};
}
}
value.into()
}