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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,15 @@ fields defining the server operation:

- **key_file**: path to the private key needed with the certificate.

- **jwt_secret**: Secret that is used to decode the JWT tokens. This should
actually be a sufficiently long randomly generated string.
One of:

- **jwt_secret_path** (recommended): Path to a file containing the JWT secret,
which is used to decode the JWT tokens.
It should contain a sufficiently long randomly generated sequence.
The contents are read without any stripping of trailing whitespace.

- **jwt_secret** (discouraged): The JWT secret, in plain form.
Discouraged, as it mixes configurations and secrets.

After the global parameters, there are configurations for different endpoints
that the server operates: IP tunnel endpoint (type: `IpEndpoint`), UDP proxy
Expand Down
37 changes: 33 additions & 4 deletions src/server/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Typically read from a JSON file. Includes certificate parameters and
//! configurations for different kinds of endpoints.

use std::{fs::File, io::BufReader, sync::LazyLock};
use std::{fs::File, io::BufReader, path::PathBuf, sync::LazyLock};

use serde::Deserialize;

Expand All @@ -21,10 +21,21 @@ use super::PsqServer;
pub struct Config {
cert_file: String,
key_file: String,
jwt_secret: String,
#[serde(flatten)]
jwt_config: JWTSecretConfig,
endpoints: Vec<Endpoint>,
}

#[derive(Clone, Debug, Deserialize)]
enum JWTSecretConfig {
// A path to a file containing a JWT secret.
#[serde(rename = "jwt_secret_path")]
FilePath(PathBuf),
// A JTW secret passed in the config file literally.
#[serde(rename = "jwt_secret")]
Plain(String),
}

static DEFAULT_CONFIG: LazyLock<Config> = LazyLock::new(|| {
serde_json::from_slice(include_bytes!("../bin/server-example.json"))
.expect("example config must parse")
Expand Down Expand Up @@ -106,8 +117,17 @@ impl Config {
}

/// Secret used to decode JWT tokens.
pub fn jwt_secret(&self) -> &String {
&self.jwt_secret
pub fn jwt_secret(&self) -> std::io::Result<Vec<u8>> {
match &self.jwt_config {
JWTSecretConfig::FilePath(path) => {
debug!("Loading JWT secret from: {:?}", path);
std::fs::read(path)
}
JWTSecretConfig::Plain(plain) => {
warn!("jwt_secret passed literally, this is discouraged. Consider using jwt_secret_path.");
Ok(plain.to_owned().into_bytes())
}
}
}

/// Return a config without endpoints configured. Used in tests.
Expand Down Expand Up @@ -205,4 +225,13 @@ mod tests {
let f = Config::read_from_file("tests/testconfig2.json");
assert!(f.is_err());
}

#[test]
fn jwt_path() {
let f = Config::read_from_file("tests/testconfig3.json").expect("must parse");
assert_eq!(
f.jwt_secret().expect("must succeed"),
b"jwt-secret-from-file"
);
}
}
2 changes: 1 addition & 1 deletion src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl PsqServer {
conn_id_seed,
clients: ClientMap::new(),
endpoints: Arc::new(Mutex::new(HashMap::new())),
jwt_secret: config.jwt_secret().as_bytes().to_vec(),
jwt_secret: config.jwt_secret()?,
retry_token_key: Key::generate(ring::hmac::HMAC_SHA256, &rng).unwrap(),
};

Expand Down
1 change: 1 addition & 0 deletions tests/jwt_secret
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jwt-secret-from-file
6 changes: 6 additions & 0 deletions tests/testconfig3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"cert_file": "src/bin/cert.crt",
"key_file": "src/bin/cert.keys",
"endpoints": [ ],
"jwt_secret_path": "tests/jwt_secret"
}