Skip to content

Commit fed1cfe

Browse files
committed
Add option to verify JWT tokens in the HTTP Authorization header
The RSA public key against which the JWT tokens are verified can be set either via a configuration file setting, or an environment variable, with the latter having the higher priority.
1 parent 56be2d9 commit fed1cfe

File tree

6 files changed

+77
-28
lines changed

6 files changed

+77
-28
lines changed

rust/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/auth-impls/src/lib.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ pub(crate) struct Claims {
4141
const BEARER_PREFIX: &str = "Bearer ";
4242

4343
impl JWTAuthorizer {
44-
/// Create new instance of [`JWTAuthorizer`]
45-
pub async fn new(jwt_issuer_key: DecodingKey) -> Self {
46-
Self { jwt_issuer_key }
44+
/// Creates a new instance of [`JWTAuthorizer`], fails on failure to parse the PEM formatted RSA public key
45+
pub async fn new(rsa_pem: &str) -> Result<Self, String> {
46+
let jwt_issuer_key =
47+
DecodingKey::from_rsa_pem(rsa_pem.as_bytes()).map_err(|e| e.to_string())?;
48+
Ok(Self { jwt_issuer_key })
4749
}
4850
}
4951

@@ -74,7 +76,7 @@ mod tests {
7476
use crate::JWTAuthorizer;
7577
use api::auth::Authorizer;
7678
use api::error::VssError;
77-
use jsonwebtoken::{encode, Algorithm, DecodingKey, EncodingKey, Header};
79+
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
7880
use serde::{Deserialize, Serialize};
7981
use std::collections::HashMap;
8082
use std::time::SystemTime;
@@ -132,7 +134,7 @@ mod tests {
132134
)
133135
.expect("Failed to create Encoding Key.");
134136

135-
let decoding_key = DecodingKey::from_rsa_pem(
137+
let decoding_key = String::from(
136138
"-----BEGIN PUBLIC KEY-----\
137139
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAysGpKU+I9i9b+QZSANu/\
138140
ExaA6w4qiQdFZaXeReiz49r1oDfABwKIFW9gK/kNnrnL9H8P+pYfj7jqUJ/glmgq\
@@ -141,12 +143,10 @@ mod tests {
141143
8YsTa5piV8KgJpG/rwYTGXuu3lcCmnWwjmbeDq1zFFrCDDVkaIHkGJgRuFIDPXaH\
142144
yUw5H2HvKlP94ySbvTDLXWZj6TyzHEHDbstqs4DgvurB/bIhi/dQ7zK3EIXL8KRB\
143145
hwIDAQAB\
144-
-----END PUBLIC KEY-----"
145-
.as_bytes(),
146-
)
147-
.expect("Failed to create Decoding Key.");
146+
-----END PUBLIC KEY-----",
147+
);
148148

149-
let jwt_authorizer = JWTAuthorizer::new(decoding_key).await;
149+
let jwt_authorizer = JWTAuthorizer::new(&decoding_key).await.unwrap();
150150

151151
let valid_jwt_token =
152152
encode(&Header::new(Algorithm::RS256), &claims, &valid_encoding_key).unwrap();

rust/server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
api = { path = "../api" }
8+
auth-impls = { path = "../auth-impls" }
89
impls = { path = "../impls" }
910

1011
hyper = { version = "1", default-features = false, features = ["server", "http1"] }

rust/server/src/main.rs

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@
1010
#![deny(missing_docs)]
1111

1212
use std::net::SocketAddr;
13+
use std::sync::Arc;
1314

1415
use tokio::net::TcpListener;
1516
use tokio::signal::unix::SignalKind;
1617

1718
use hyper::server::conn::http1;
1819
use hyper_util::rt::TokioIo;
1920

20-
use crate::vss_service::VssService;
2121
use api::auth::{Authorizer, NoopAuthorizer};
2222
use api::kv_store::KvStore;
23+
use auth_impls::JWTAuthorizer;
2324
use impls::postgres_store::{Certificate, PostgresPlaintextBackend, PostgresTlsBackend};
24-
use std::sync::Arc;
25+
use util::config::{Config, ServerConfig};
26+
use vss_service::VssService;
2527

26-
pub(crate) mod util;
27-
pub(crate) mod vss_service;
28+
mod util;
29+
mod vss_service;
2830

2931
fn main() {
3032
let args: Vec<String> = std::env::args().collect();
@@ -33,22 +35,21 @@ fn main() {
3335
std::process::exit(1);
3436
}
3537

36-
let config = match util::config::load_config(&args[1]) {
37-
Ok(cfg) => cfg,
38-
Err(e) => {
39-
eprintln!("Failed to load configuration: {}", e);
40-
std::process::exit(1);
41-
},
42-
};
43-
44-
let addr: SocketAddr =
45-
match format!("{}:{}", config.server_config.host, config.server_config.port).parse() {
46-
Ok(addr) => addr,
38+
let Config { server_config: ServerConfig { host, port }, jwt_auth_config, postgresql_config } =
39+
match util::config::load_config(&args[1]) {
40+
Ok(cfg) => cfg,
4741
Err(e) => {
48-
eprintln!("Invalid host/port configuration: {}", e);
42+
eprintln!("Failed to load configuration: {}", e);
4943
std::process::exit(1);
5044
},
5145
};
46+
let addr: SocketAddr = match format!("{}:{}", host, port).parse() {
47+
Ok(addr) => addr,
48+
Err(e) => {
49+
eprintln!("Invalid host/port configuration: {}", e);
50+
std::process::exit(1);
51+
},
52+
};
5253

5354
let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() {
5455
Ok(runtime) => Arc::new(runtime),
@@ -66,9 +67,33 @@ fn main() {
6667
std::process::exit(-1);
6768
},
6869
};
69-
let authorizer: Arc<dyn Authorizer> = Arc::new(NoopAuthorizer {});
70+
71+
let rsa_pem_env = match std::env::var("VSS_JWT_RSA_PEM") {
72+
Ok(env) => Some(env),
73+
Err(std::env::VarError::NotPresent) => None,
74+
Err(e) => {
75+
println!("Failed to load the VSS_JWT_RSA_PEM env var: {}", e);
76+
std::process::exit(-1);
77+
},
78+
};
79+
let rsa_pem = rsa_pem_env.or(jwt_auth_config.map(|config| config.rsa_pem));
80+
let authorizer: Arc<dyn Authorizer> = if let Some(pem) = rsa_pem {
81+
let authorizer = match JWTAuthorizer::new(pem.as_str()).await {
82+
Ok(auth) => auth,
83+
Err(e) => {
84+
println!("Failed to parse the PEM formatted RSA public key: {}", e);
85+
std::process::exit(-1);
86+
},
87+
};
88+
println!("Configured JWT authorizer with RSA public key");
89+
Arc::new(authorizer)
90+
} else {
91+
println!("No JWT authentication method configured");
92+
Arc::new(NoopAuthorizer {})
93+
};
94+
7095
let postgresql_config =
71-
config.postgresql_config.expect("PostgreSQLConfig must be defined in config file.");
96+
postgresql_config.expect("PostgreSQLConfig must be defined in config file.");
7297
let endpoint = postgresql_config.to_postgresql_endpoint();
7398
let db_name = postgresql_config.database;
7499
let store: Arc<dyn KvStore> = if let Some(tls_config) = postgresql_config.tls {
@@ -109,6 +134,7 @@ fn main() {
109134
Arc::new(postgres_plaintext_backend)
110135
};
111136
println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, db_name);
137+
112138
let rest_svc_listener =
113139
TcpListener::bind(&addr).await.expect("Failed to bind listening port");
114140
println!("Listening for incoming connections on {}", addr);

rust/server/src/util/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use serde::Deserialize;
33
#[derive(Deserialize)]
44
pub(crate) struct Config {
55
pub(crate) server_config: ServerConfig,
6+
pub(crate) jwt_auth_config: Option<JwtAuthConfig>,
67
pub(crate) postgresql_config: Option<PostgreSQLConfig>,
78
}
89

@@ -12,6 +13,11 @@ pub(crate) struct ServerConfig {
1213
pub(crate) port: u16,
1314
}
1415

16+
#[derive(Deserialize)]
17+
pub(crate) struct JwtAuthConfig {
18+
pub(crate) rsa_pem: String,
19+
}
20+
1521
#[derive(Deserialize)]
1622
pub(crate) struct PostgreSQLConfig {
1723
pub(crate) username: Option<String>, // Optional in TOML, can be overridden by env

rust/server/vss-server-config.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
host = "127.0.0.1"
33
port = 8080
44

5+
# Uncomment the table below to verify JWT tokens in the HTTP Authorization header against the given RSA public key,
6+
# can be overridden by env var `VSS_JWT_RSA_PEM`
7+
# [jwt_auth_config]
8+
# rsa_pem = """
9+
# -----BEGIN PUBLIC KEY-----
10+
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAstPJs4ut+tFAI0qrOyGt
11+
# /3FN5jWc5gLv/j9Rc6lgr4hm7lyR05PU/G+4rfxdXGNyGTlQ6dRqcVy78CjxWz9f
12+
# 8l08EKLERPh8JhE5el6vr+ehWD5iQxSP3ejpx0Mr977fKMNKg6jlFiL+y50hOEp2
13+
# 6iN9QzZQjLxotDT3aQvbCA/DZpI+fV6WKDKWGS+pZGDVgOz5x/RcStJQXxkX3ACK
14+
# WhVdrtN3h6mHlhIt7ZIqVvQmY4NL03QPyljt13sYHoiFaoxINF/funBMCjrfSLcB
15+
# ko1rWE2BWdOrFqi27RtBs5AHOSAWXuz/2SUGpFuTQuJi7U68QUfjKeQO46JpQf+v
16+
# kQIDAQAB
17+
# -----END PUBLIC KEY-----
18+
# """
19+
520
[postgresql_config]
621
username = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_USERNAME`
722
password = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_PASSWORD`

0 commit comments

Comments
 (0)