Store jwt secret in database

This commit is contained in:
SeanOMik 2023-07-14 16:30:12 -04:00
parent 95914653e0
commit 7cc19bc1cd
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
5 changed files with 76 additions and 17 deletions

View File

@ -59,9 +59,8 @@ pub struct AuthResponse {
issued_at: String, issued_at: String,
} }
/// In the returned UserToken::user, only the username is specified fn create_jwt_token(jwt_key: String, account: Option<&str>, scopes: Vec<Scope>) -> anyhow::Result<TokenInfo> {
fn create_jwt_token(account: Option<&str>, scopes: Vec<Scope>) -> anyhow::Result<TokenInfo> { let key: Hmac<Sha256> = Hmac::new_from_slice(jwt_key.as_bytes())?;
let key: Hmac<Sha256> = Hmac::new_from_slice(b"some-secret")?;
let now = chrono::offset::Utc::now(); let now = chrono::offset::Utc::now();
@ -186,11 +185,12 @@ pub async fn auth_basic_get(
scope.actions.retain(|a| *a == Action::Pull); scope.actions.retain(|a| *a == Action::Pull);
} }
let token = create_jwt_token(None, auth.scope).map_err(|_| { let token = create_jwt_token(state.config.jwt_key.clone(), None, auth.scope)
error!("Failed to create jwt token!"); .map_err(|_| {
error!("Failed to create jwt token!");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
let token_str = token.token; let token_str = token.token;
let now_format = format!("{}", token.created_at.format("%+")); let now_format = format!("{}", token.created_at.format("%+"));
@ -290,11 +290,12 @@ pub async fn auth_basic_get(
debug!("User password is correct"); debug!("User password is correct");
let now = SystemTime::now(); let now = SystemTime::now();
let token = create_jwt_token(Some(account), vec![]).map_err(|_| { let token = create_jwt_token(state.config.jwt_key.clone(), Some(account), vec![])
error!("Failed to create jwt token!"); .map_err(|_| {
error!("Failed to create jwt token!");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
let token_str = token.token; let token_str = token.token;
debug!("Created jwt token"); debug!("Created jwt token");

View File

@ -71,6 +71,8 @@ pub struct Config {
pub database: DatabaseConfig, pub database: DatabaseConfig,
pub storage: StorageConfig, pub storage: StorageConfig,
pub tls: Option<TlsConfig>, pub tls: Option<TlsConfig>,
#[serde(skip)]
pub jwt_key: String,
} }
#[allow(dead_code)] #[allow(dead_code)]

View File

@ -1,5 +1,8 @@
use async_trait::async_trait; use async_trait::async_trait;
use sqlx::{Sqlite, Pool}; use hmac::{Hmac, digest::KeyInit};
use rand::{Rng, distributions::Alphanumeric};
use sha2::Sha256;
use sqlx::{Sqlite, Pool, sqlite::SqliteError};
use tracing::{debug, warn}; use tracing::{debug, warn};
use chrono::{DateTime, Utc, NaiveDateTime, TimeZone}; use chrono::{DateTime, Utc, NaiveDateTime, TimeZone};
@ -8,12 +11,13 @@ use crate::dto::{Tag, user::{User, RepositoryPermissions, RegistryUserType, Perm
#[async_trait] #[async_trait]
pub trait Database { pub trait Database {
// Digest related functions // Digest related functions
/// Create the tables in the database /// Create the tables in the database
async fn create_schema(&self) -> anyhow::Result<()>; async fn create_schema(&self) -> anyhow::Result<()>;
async fn get_jwt_secret(&self) -> anyhow::Result<String>;
// Tag related functions // Tag related functions
/// Get tags associated with a repository /// Get tags associated with a repository
@ -67,14 +71,56 @@ pub trait Database {
#[async_trait] #[async_trait]
impl Database for Pool<Sqlite> { impl Database for Pool<Sqlite> {
async fn create_schema(&self) -> anyhow::Result<()> { async fn create_schema(&self) -> anyhow::Result<()> {
sqlx::query(include_str!("schemas/schema.sql")) let orca_version = "0.1.0";
.execute(self).await?; let schema_version = "0.0.1";
debug!("Created database schema"); let row: Option<(u32, )> = match sqlx::query_as("SELECT COUNT(1) FROM orca WHERE \"schema_version\" = ?")
.bind(schema_version)
.fetch_one(self).await {
Ok(row) => Some(row),
Err(e) => match e {
sqlx::Error::RowNotFound => {
None
},
// ignore no such table errors
sqlx::Error::Database(b) if b.message().starts_with("no such table") => None,
_ => {
return Err(anyhow::Error::new(e));
}
}
};
if row.is_none() || row.unwrap().0 == 0 {
let jwt_sec: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect();
// create schema
// TODO: Check if needed
sqlx::query(include_str!("schemas/schema.sql"))
.execute(self).await?;
debug!("Created database schema");
sqlx::query("INSERT INTO orca(orca_version, schema_version, jwt_secret) VALUES (?, ?, ?)")
.bind(orca_version)
.bind(schema_version)
.bind(jwt_sec)
.execute(self).await?;
debug!("Inserted information about orca!");
}
Ok(()) Ok(())
} }
async fn get_jwt_secret(&self) -> anyhow::Result<String> {
let rows: (String, ) = sqlx::query_as("SELECT jwt_secret FROM orca WHERE id = (SELECT max(id) FROM orca)")
.fetch_one(self).await?;
Ok(rows.0)
}
async fn link_manifest_layer(&self, manifest_digest: &str, layer_digest: &str) -> anyhow::Result<()> { async fn link_manifest_layer(&self, manifest_digest: &str, layer_digest: &str) -> anyhow::Result<()> {
sqlx::query("INSERT INTO manifest_layers(manifest, layer_digest) VALUES (?, ?)") sqlx::query("INSERT INTO manifest_layers(manifest, layer_digest) VALUES (?, ?)")
.bind(manifest_digest) .bind(manifest_digest)

View File

@ -1,3 +1,10 @@
CREATE TABLE IF NOT EXISTS orca (
id INTEGER PRIMARY KEY AUTOINCREMENT,
orca_version TEXT NOT NULL,
schema_version TEXT NOT NULL,
jwt_secret TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS projects ( CREATE TABLE IF NOT EXISTS projects (
name TEXT NOT NULL UNIQUE PRIMARY KEY, name TEXT NOT NULL UNIQUE PRIMARY KEY,
-- 0 = private, 1 = public -- 0 = private, 1 = public

View File

@ -69,7 +69,7 @@ async fn change_request_paths<B>(mut request: Request<B>, next: Next<B>) -> Resu
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let config = Config::new() let mut config = Config::new()
.expect("Failure to parse config!"); .expect("Failure to parse config!");
tracing_subscriber::fmt() tracing_subscriber::fmt()
@ -92,6 +92,9 @@ async fn main() -> anyhow::Result<()> {
.connect_with(connection_options).await?; .connect_with(connection_options).await?;
pool.create_schema().await?; pool.create_schema().await?;
// set jwt key
config.jwt_key = pool.get_jwt_secret().await?;
let storage_driver: Mutex<Box<dyn StorageDriver>> = match &config.storage { let storage_driver: Mutex<Box<dyn StorageDriver>> = match &config.storage {
StorageConfig::Filesystem(fs) => { StorageConfig::Filesystem(fs) => {
Mutex::new(Box::new(FilesystemDriver::new(&fs.path))) Mutex::new(Box::new(FilesystemDriver::new(&fs.path)))