Updated auth to use pem keys instead of base64 keys in strings?
This commit is contained in:
@@ -11,7 +11,7 @@ DATABASE_PORT=5432
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
|
||||
MINIO_ROOT_USER=siren
|
||||
MINIO_ROOT_USER=weather
|
||||
MINIO_ROOT_PASSWORD=7LtSkxU15ix40nu
|
||||
MINIO_HOST=localhost
|
||||
MINIO_PORT=9000
|
||||
@@ -20,12 +20,8 @@ MINIO_PORT_INTERNAL=9001
|
||||
SERVICE_HOST=localhost
|
||||
SERVICE_PORT=5000
|
||||
|
||||
ACCESS_TOKEN_PRIVATE_KEY=
|
||||
ACCESS_TOKEN_PUBLIC_KEY=
|
||||
KEYS_DIR_PATH=
|
||||
ACCESS_TOKEN_MAXAGE=5
|
||||
|
||||
REFRESH_TOKEN_PRIVATE_KEY=
|
||||
REFRESH_TOKEN_PUBLIC_KEY=
|
||||
REFRESH_TOKEN_MAXAGE=30
|
||||
|
||||
GOV_API_URL=https://aviationweather.gov/cgi-bin/data
|
||||
|
||||
1
service/Cargo.lock
generated
1
service/Cargo.lock
generated
@@ -1805,7 +1805,6 @@ dependencies = [
|
||||
"actix-web",
|
||||
"actix-web-httpauth",
|
||||
"argon2",
|
||||
"base64",
|
||||
"chrono",
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
|
||||
@@ -32,5 +32,4 @@ log = "0.4.20"
|
||||
argon2 = "0.5.2"
|
||||
jsonwebtoken = "9.0.0"
|
||||
redis = { version = "0.23.3", features = ["tokio-comp", "connection-manager", "r2d2"] }
|
||||
base64 = "0.21.4"
|
||||
rustix = "0.38.19" # https://github.com/imsnif/bandwhich/issues/284
|
||||
|
||||
@@ -11,14 +11,26 @@ COPY Cargo.toml ./
|
||||
RUN apt-get update && apt-get install -y cmake
|
||||
RUN cargo build --release
|
||||
|
||||
# ======
|
||||
# Keys
|
||||
# ======
|
||||
FROM debian:bookworm-slim as keys
|
||||
WORKDIR /keys
|
||||
|
||||
RUN apt-get update && apt-get install -y openssl libpq-dev
|
||||
RUN openssl genrsa -out access.pem 4096
|
||||
RUN openssl rsa -in access.pem -pubout -outform PEM -out access.pem.pub
|
||||
RUN openssl genrsa -out refresh.pem 4096
|
||||
RUN openssl rsa -in refresh.pem -pubout -outform PEM -out refresh.pem.pub
|
||||
|
||||
# =========
|
||||
# Runtime
|
||||
# =========
|
||||
FROM debian:bookworm-slim as runtime
|
||||
FROM keys as runtime
|
||||
WORKDIR /service
|
||||
USER root
|
||||
|
||||
COPY --from=builder /builder/target/release/service /usr/local/bin/service
|
||||
COPY --from=packages /packages /usr/bin
|
||||
COPY --from=keys /keys /keys
|
||||
|
||||
CMD ["service"]
|
||||
|
||||
@@ -16,6 +16,7 @@ build: ## Build the Docker image
|
||||
utils: ## Start the utils
|
||||
docker compose up -d db
|
||||
docker compose up -d redis
|
||||
docker compose up -d minio
|
||||
|
||||
up: ## Start the Docker containers
|
||||
docker compose up -d
|
||||
@@ -36,4 +37,10 @@ clean-db: ## Remove database
|
||||
docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "DROP DATABASE IF EXISTS \"${DATABASE_NAME}\";"'
|
||||
docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "CREATE DATABASE \"${DATABASE_NAME}\";"' || true
|
||||
|
||||
generate: ## Generate RSA keys
|
||||
mkdir ../keys/
|
||||
openssl genrsa -out ../keys/access_private_key.pem 4096
|
||||
openssl rsa -in ../keys/access_private_key.pem -pubout -outform PEM -out ../keys/access_public_key.pem
|
||||
openssl genrsa -out ../keys/refresh_private_key.pem 4096
|
||||
openssl rsa -in ../keys/refresh_private_key.pem -pubout -outform PEM -out ../keys/refresh_public_key.pem
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# Aviation Weather
|
||||
|
||||
## UI
|
||||
|
||||
## Service
|
||||
|
||||
## REST API
|
||||
The REST API for the weather service is described below.
|
||||
|
||||
### Import Airports
|
||||
---
|
||||
#### Request
|
||||
`GET /import`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Get Airports
|
||||
---
|
||||
#### Request
|
||||
`GET /airports`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Get Airport
|
||||
---
|
||||
#### Request
|
||||
`GET /airports/{icao}`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Create Airport
|
||||
---
|
||||
#### Request
|
||||
`CREATE /airports`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Update Airport
|
||||
---
|
||||
#### Request
|
||||
`PUT /airports/{icao}`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Delete Airport
|
||||
---
|
||||
#### Request
|
||||
`DELETE /airports/{icao}`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
@@ -54,6 +54,8 @@ services:
|
||||
REDIS_PORT: 6379
|
||||
SERVICE_HOST: service
|
||||
SERVICE_PORT: 5000
|
||||
volumes:
|
||||
- ${KEYS_DIR_PATH}:/keys
|
||||
ports:
|
||||
- "${SERVICE_PORT:-5000}:5000"
|
||||
build:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::env;
|
||||
|
||||
use argon2::{password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError}, Argon2, PasswordHash};
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey, Header, encode, decode, Validation, Algorithm};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -31,9 +30,7 @@ pub struct TokenDetails {
|
||||
}
|
||||
|
||||
pub fn verify_token(token: &str, public_key: &str) -> Result<TokenDetails, ServiceError> {
|
||||
let bytes_public_key = general_purpose::STANDARD.decode(public_key).unwrap();
|
||||
let decoded_public_key = String::from_utf8(bytes_public_key).unwrap();
|
||||
let key = DecodingKey::from_rsa_pem(decoded_public_key.as_bytes())?;
|
||||
let key = DecodingKey::from_rsa_pem(public_key.as_bytes())?;
|
||||
let validation = Validation::new(Algorithm::RS256);
|
||||
let decoded = decode::<TokenClaims>(token, &key, &validation)?;
|
||||
let email = decoded.claims.sub;
|
||||
@@ -43,21 +40,21 @@ pub fn verify_token(token: &str, public_key: &str) -> Result<TokenDetails, Servi
|
||||
|
||||
pub fn generate_access_token(email: &str) -> Result<TokenDetails, ServiceError> {
|
||||
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||
let access_private_key = env::var("ACCESS_TOKEN_PRIVATE_KEY")
|
||||
.expect("ACCESS_TOKEN_PRIVATE_KEY must be set");
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||
let keys_dir = env::var("KEYS_DIR_PATH")?;
|
||||
let access_private_key = std::fs::read_to_string(format!("{}/access_private_key.pem", keys_dir))?;
|
||||
generate_token(&email, access_token_max_age, &access_private_key)
|
||||
}
|
||||
|
||||
pub fn generate_refresh_token(email: &str) -> Result<TokenDetails, ServiceError> {
|
||||
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||
let refresh_private_key = env::var("REFRESH_TOKEN_PRIVATE_KEY")
|
||||
.expect("REFRESH_TOKEN_PRIVATE_KEY must be set");
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||
let keys_dir = env::var("KEYS_DIR_PATH")?;
|
||||
let refresh_private_key = std::fs::read_to_string(format!("{}/refresh_private_key.pem", keys_dir))?;
|
||||
generate_token(&email, refresh_token_max_age, &refresh_private_key)
|
||||
}
|
||||
|
||||
@@ -78,9 +75,7 @@ pub fn generate_token(email: &str, ttl: i64, private_key: &str) -> Result<TokenD
|
||||
nbf: now.timestamp()
|
||||
};
|
||||
let header = Header::new(Algorithm::RS256);
|
||||
let bytes_private_key = general_purpose::STANDARD.decode(private_key).unwrap();
|
||||
let decoded_private_key = String::from_utf8(bytes_private_key).unwrap();
|
||||
let key = EncodingKey::from_rsa_pem(decoded_private_key.as_bytes())?;
|
||||
let key = EncodingKey::from_rsa_pem(private_key.as_bytes())?;
|
||||
let token = encode(&header, &claims, &key)?;
|
||||
token_details.token = Some(token);
|
||||
Ok(token_details)
|
||||
|
||||
@@ -157,8 +157,8 @@ impl FromRequest for JwtAuth {
|
||||
})))
|
||||
};
|
||||
|
||||
let public_key = env::var("ACCESS_TOKEN_PUBLIC_KEY")
|
||||
.expect("ACCESS_TOKEN_PUBLIC_KEY must be set");
|
||||
let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set");
|
||||
let public_key = std::fs::read_to_string(format!("{}/access_public_key.pem", keys_dir)).expect("Failed to read access public key");
|
||||
|
||||
let access_token_details = match verify_token(&access_token, &public_key) {
|
||||
Ok(token_details) => token_details,
|
||||
|
||||
@@ -150,8 +150,9 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
})
|
||||
};
|
||||
|
||||
let public_key = env::var("REFRESH_TOKEN_PUBLIC_KEY")
|
||||
.expect("REFRESH_TOKEN_PUBLIC_KEY must be set");
|
||||
let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set");
|
||||
let public_key = std::fs::read_to_string(format!("{}/refresh_public_key.pem", keys_dir))
|
||||
.expect("Unable to read refresh public key");
|
||||
let refresh_token_details = match verify_token(&refresh_token, &public_key) {
|
||||
Ok(token_details) => token_details,
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
@@ -181,8 +182,9 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
match req.cookie("access_token") {
|
||||
Some(cookie) => {
|
||||
let access_token = cookie.value().to_string();
|
||||
let public_key = env::var("ACCESS_TOKEN_PUBLIC_KEY")
|
||||
.expect("ACCESS_TOKEN_PUBLIC_KEY must be set");
|
||||
let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set");
|
||||
let public_key = std::fs::read_to_string(format!("{}/access_public_key.pem", keys_dir))
|
||||
.expect("Unable to read access public key");
|
||||
match verify_token(&access_token, &public_key) {
|
||||
Ok(token_details) => {
|
||||
let _: redis::RedisResult<()> = conn.del(token_details.token_uuid.to_string()).await;
|
||||
@@ -285,8 +287,9 @@ async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
|
||||
message: "Refresh token not found".to_string()
|
||||
})
|
||||
};
|
||||
let public_key = env::var("REFRESH_TOKEN_PUBLIC_KEY")
|
||||
.expect("REFRESH_TOKEN_PUBLIC_KEY must be set");
|
||||
let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set");
|
||||
let public_key = std::fs::read_to_string(format!("{}/refresh_public_key.pem", keys_dir))
|
||||
.expect("Unable to read refresh public key");
|
||||
let refresh_token_details = match verify_token(&refresh_token, &public_key) {
|
||||
Ok(token_details) => token_details,
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
|
||||
@@ -38,6 +38,18 @@ impl fmt::Display for ServiceError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ServiceError {
|
||||
fn from(error: std::io::Error) -> ServiceError {
|
||||
ServiceError::new(500, format!("Unknown IO error: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::env::VarError> for ServiceError {
|
||||
fn from(error: std::env::VarError) -> ServiceError {
|
||||
ServiceError::new(500, format!("Unknown environment variable error: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DieselError> for ServiceError {
|
||||
fn from(error: DieselError) -> ServiceError {
|
||||
match error {
|
||||
|
||||
Reference in New Issue
Block a user