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

View File

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

View File

@ -1,5 +1,8 @@
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 chrono::{DateTime, Utc, NaiveDateTime, TimeZone};
@ -8,12 +11,13 @@ use crate::dto::{Tag, user::{User, RepositoryPermissions, RegistryUserType, Perm
#[async_trait]
pub trait Database {
// Digest related functions
/// Create the tables in the database
async fn create_schema(&self) -> anyhow::Result<()>;
async fn get_jwt_secret(&self) -> anyhow::Result<String>;
// Tag related functions
/// Get tags associated with a repository
@ -67,14 +71,56 @@ pub trait Database {
#[async_trait]
impl Database for Pool<Sqlite> {
async fn create_schema(&self) -> anyhow::Result<()> {
sqlx::query(include_str!("schemas/schema.sql"))
.execute(self).await?;
let orca_version = "0.1.0";
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(())
}
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<()> {
sqlx::query("INSERT INTO manifest_layers(manifest, layer_digest) VALUES (?, ?)")
.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 (
name TEXT NOT NULL UNIQUE PRIMARY KEY,
-- 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]
async fn main() -> anyhow::Result<()> {
let config = Config::new()
let mut config = Config::new()
.expect("Failure to parse config!");
tracing_subscriber::fmt()
@ -92,6 +92,9 @@ async fn main() -> anyhow::Result<()> {
.connect_with(connection_options).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 {
StorageConfig::Filesystem(fs) => {
Mutex::new(Box::new(FilesystemDriver::new(&fs.path)))