diff --git a/common/src/config.rs b/common/src/config.rs index 0a54ff697..be1833c4a 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -13,7 +13,6 @@ pub struct Config { pub base_dir: PathBuf, pub log: LogConfig, pub database: DbConfig, - pub ssh: SshConfig, pub storage: StorageConfig, pub monorepo: MonoConfig, pub pack: PackConfig, @@ -188,19 +187,6 @@ impl Default for DbConfig { } } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SshConfig { - pub ssh_key_path: PathBuf, -} - -impl Default for SshConfig { - fn default() -> Self { - Self { - ssh_key_path: PathBuf::from("/tmp/.mega/ssh"), - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct StorageConfig { pub obs_access_key: String, @@ -223,12 +209,14 @@ impl Default for StorageConfig { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct MonoConfig { pub import_dir: PathBuf, + pub disable_http_push: bool, } impl Default for MonoConfig { fn default() -> Self { Self { import_dir: PathBuf::from("/third-part"), + disable_http_push: false, } } } diff --git a/docker/config.toml b/docker/config.toml index 4c4023cb7..f9483ee5f 100644 --- a/docker/config.toml +++ b/docker/config.toml @@ -36,9 +36,6 @@ min_connection = 8 sqlx_logging = false -[ssh] -ssh_key_path = "${base_dir}/etc/ssh" - [storage] obs_access_key = "" @@ -56,6 +53,7 @@ obs_endpoint = "https://obs.cn-east-3.myhuaweicloud.com" ## Mega treats files under this directory as import repo and other directories as monorepo import_dir = "/third-part" +disable_http_push = false [pack] # The maximum memory used by decode, Unit is GB diff --git a/docker/mono-pg-dockerfile b/docker/mono-pg-dockerfile index a28d02bd2..4804d1199 100644 --- a/docker/mono-pg-dockerfile +++ b/docker/mono-pg-dockerfile @@ -17,6 +17,6 @@ EXPOSE 5432 # Add the database initialization script to the container # When the container starts, PostgreSQL will automatically execute all .sql files in the docker-entrypoint-initdb.d/ directory -COPY ./sql/postgres/pg_20240910__init.sql /docker-entrypoint-initdb.d/ +COPY ./sql/postgres/pg_20240912__init.sql /docker-entrypoint-initdb.d/ CMD ["postgres"] diff --git a/docs/development.md b/docs/development.md index 06a2c6d83..4243fc2df 100644 --- a/docs/development.md +++ b/docs/development.md @@ -273,9 +273,6 @@ min_connection = 16 sqlx_logging = false -[ssh] -ssh_key_path = "${base_dir}/ssh" - [storage] obs_access_key = "" @@ -293,6 +290,7 @@ obs_endpoint = "https://obs.cn-east-3.myhuaweicloud.com" ## Mega treats files under this directory as import repo and other directories as monorepo import_dir = "/third-part" +disable_http_push = false [pack] # The maximum memory used by decode, Unit is GB diff --git a/jupiter/callisto/src/ssh_keys.rs b/jupiter/callisto/src/ssh_keys.rs index 2aa894505..a018f29bc 100644 --- a/jupiter/callisto/src/ssh_keys.rs +++ b/jupiter/callisto/src/ssh_keys.rs @@ -9,6 +9,8 @@ pub struct Model { pub id: i64, pub user_id: i64, #[sea_orm(column_type = "Text")] + pub title: String, + #[sea_orm(column_type = "Text")] pub ssh_key: String, #[sea_orm(column_type = "Text")] pub finger: String, diff --git a/jupiter/src/storage/init.rs b/jupiter/src/storage/init.rs index c9dc052fb..a04dd7bee 100644 --- a/jupiter/src/storage/init.rs +++ b/jupiter/src/storage/init.rs @@ -51,7 +51,7 @@ async fn setup_sql(conn: &DatabaseConnection) -> Result<(), TransactionError Result<(), MegaError> { let model = ssh_keys::Model { id: generate_id(), user_id, + title: title.to_owned(), ssh_key: ssh_key.to_owned(), finger: finger.to_owned(), created_at: chrono::Utc::now().naive_utc(), diff --git a/mega/config.toml b/mega/config.toml index f939074f3..303643207 100644 --- a/mega/config.toml +++ b/mega/config.toml @@ -36,9 +36,6 @@ min_connection = 16 sqlx_logging = false -[ssh] -ssh_key_path = "${base_dir}/ssh" - [storage] obs_access_key = "" @@ -56,6 +53,7 @@ obs_endpoint = "https://obs.cn-east-3.myhuaweicloud.com" ## Mega treats files under this directory as import repo and other directories as monorepo import_dir = "/third-part" +disable_http_push = false [pack] # The maximum memory used by decode, Unit is GB diff --git a/mono/Cargo.toml b/mono/Cargo.toml index fc72008a6..9038c37b0 100644 --- a/mono/Cargo.toml +++ b/mono/Cargo.toml @@ -18,6 +18,7 @@ callisto = { workspace = true } jupiter = { workspace = true } ceres = { workspace = true } taurus = { workspace = true } +vault = { workspace = true } anyhow = { workspace = true } axum = { workspace = true } diff --git a/mono/config.toml b/mono/config.toml index ac0dffa15..539d7cbae 100644 --- a/mono/config.toml +++ b/mono/config.toml @@ -36,9 +36,6 @@ min_connection = 16 sqlx_logging = false -[ssh] -ssh_key_path = "${base_dir}/ssh" - [storage] obs_access_key = "" @@ -56,6 +53,7 @@ obs_endpoint = "https://obs.cn-east-3.myhuaweicloud.com" ## Mega treats files under this directory as import repo and other directories as monorepo import_dir = "/third-part" +disable_http_push = false [pack] # The maximum memory used by decode, Unit is GB diff --git a/mono/src/api/user/model.rs b/mono/src/api/user/model.rs index 03903f26c..65794af6d 100644 --- a/mono/src/api/user/model.rs +++ b/mono/src/api/user/model.rs @@ -4,12 +4,14 @@ use sea_orm::entity::prelude::*; #[derive(Debug, Deserialize)] pub struct AddSSHKey { + pub title: String, pub ssh_key: String, } #[derive(Debug, Serialize, Deserialize)] pub struct ListSSHKey { pub id: i64, + pub title: String, pub ssh_key: String, pub finger: String, pub created_at: DateTime, @@ -19,6 +21,7 @@ impl From for ListSSHKey { fn from(value: ssh_keys::Model) -> Self { Self { id: value.id, + title: value.title, ssh_key: value.ssh_key, finger: value.finger, created_at: value.created_at, diff --git a/mono/src/api/user/user_router.rs b/mono/src/api/user/user_router.rs index b082fb91b..e2e93d2a3 100644 --- a/mono/src/api/user/user_router.rs +++ b/mono/src/api/user/user_router.rs @@ -7,10 +7,10 @@ use russh_keys::parse_public_key_base64; use common::model::CommonResult; -use crate::api::{error::ApiError, oauth::model::LoginUser}; use crate::api::user::model::AddSSHKey; use crate::api::user::model::ListSSHKey; use crate::api::MonoApiServiceState; +use crate::api::{error::ApiError, oauth::model::LoginUser}; pub fn routers() -> Router { Router::new() @@ -32,20 +32,24 @@ async fn add_key( state: State, Json(json): Json, ) -> Result>, ApiError> { - let key_data = json - .ssh_key - .split_whitespace() - .nth(1) - .ok_or("Invalid key format") - .unwrap(); - - let key = parse_public_key_base64(key_data)?; + let ssh_key: Vec<&str> = json.ssh_key.split_whitespace().collect(); + let key = parse_public_key_base64(ssh_key.get(1).ok_or("Invalid key format").unwrap())?; + let title = if !json.title.is_empty() { + json.title + } else { + ssh_key + .get(2) + .ok_or("Invalid key format") + .unwrap() + .to_owned() + .to_owned() + }; let res = state .context .services .user_storage - .save_ssh_key(user.user_id, &json.ssh_key, &key.fingerprint()) + .save_ssh_key(user.user_id, &title, &json.ssh_key, &key.fingerprint()) .await; let res = match res { Ok(_) => CommonResult::success(None), diff --git a/mono/src/server/https_server.rs b/mono/src/server/https_server.rs index e8dc9ba00..164ed0a27 100644 --- a/mono/src/server/https_server.rs +++ b/mono/src/server/https_server.rs @@ -145,8 +145,7 @@ pub async fn app(config: Config, host: String, port: u16, common: CommonOptions) } lazy_static! { - /// The following regular expressions are used to match the LFS server discovery protocol. - /// Git Protocol + /// The following regular expressions are used to match the Git server protocol. static ref INFO_REFS_REGEX: Regex = Regex::new(r"/info/refs$").unwrap(); static ref REGEX_GIT_UPLOAD_PACK: Regex = Regex::new(r"/git-upload-pack$").unwrap(); static ref REGEX_GIT_RECEIVE_PACK: Regex = Regex::new(r"/git-receive-pack$").unwrap(); @@ -186,6 +185,12 @@ pub async fn post_method_router( pack_protocol.service_type = Some(ServiceType::UploadPack); crate::git_protocol::http::git_upload_pack(req, pack_protocol).await } else if REGEX_GIT_RECEIVE_PACK.is_match(uri.path()) { + if state.context.config.monorepo.disable_http_push { + return Err(( + StatusCode::FORBIDDEN, + String::from("HTTP Push Has Been Disabled"), + )) + } let mut pack_protocol = SmartProtocol::new( remove_git_suffix(uri.clone(), "/git-receive-pack"), state.context.clone(), diff --git a/mono/src/server/ssh_server.rs b/mono/src/server/ssh_server.rs index c6f39d7ad..b849daa53 100644 --- a/mono/src/server/ssh_server.rs +++ b/mono/src/server/ssh_server.rs @@ -1,18 +1,14 @@ use std::collections::HashMap; -use std::fs::{create_dir_all, File}; -use std::io::Read; use std::net::SocketAddr; -use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; -use anyhow::Result; use bytes::BytesMut; use clap::Args; use common::config::Config; use ed25519_dalek::pkcs8::spki::der::pem::LineEnding; -use ed25519_dalek::pkcs8::{DecodePrivateKey, EncodePrivateKey, EncodePublicKey}; +use ed25519_dalek::pkcs8::{DecodePrivateKey, EncodePrivateKey}; use ed25519_dalek::SigningKey; use russh::server::Server; use russh_keys::key::KeyPair; @@ -20,6 +16,7 @@ use russh_keys::key::KeyPair; use common::model::CommonOptions; use jupiter::context::Context; use tokio::sync::Mutex; +use vault::vault::{read_secret, write_secret}; use crate::git_protocol::ssh::SshServer; @@ -36,18 +33,12 @@ pub struct SshOptions { pub struct SshCustom { #[arg(long, default_value_t = 2222)] ssh_port: u16, - - #[arg(long, value_name = "FILE")] - ssh_key_path: Option, - - #[arg(long, value_name = "FILE")] - ssh_cert_path: Option, } /// start a ssh server pub async fn start_server(config: Config, command: &SshOptions) { // we need to persist the key to prevent key expired after server restart. - let client_key = load_key(config.ssh.ssh_key_path.clone()).unwrap(); + let client_key = load_key(); let client_pubkey = Arc::new(client_key.clone_public_key().unwrap()); let mut ru_config = russh::server::Config { @@ -65,12 +56,7 @@ pub async fn start_server(config: Config, command: &SshOptions) { let SshOptions { common: CommonOptions { host, .. }, - custom: - SshCustom { - ssh_port, - ssh_key_path: _, - ssh_cert_path: _, - }, + custom: SshCustom { ssh_port }, } = command; let context = Context::new(config).await; let mut ssh_server = SshServer { @@ -86,51 +72,28 @@ pub async fn start_server(config: Config, command: &SshOptions) { ssh_server.run_on_address(ru_config, addr).await.unwrap(); } -/// # Loads an SSH keypair. -/// -/// This function follows the following steps: -/// 1. It retrieves the root directory for the SSH key from path. -/// 2. It constructs the path to the SSH private key file by joining the root directory with the filename "id_rsa" using PathBuf. -/// 3. It checks if the key file exists. If it doesn't, it generates a new Ed25519 keypair using KeyPair::generate_ed25519. -/// - The generated keypair is then written to the key file. -/// 4. If the key file exists, it reads the keypair from the file. -/// - The keypair is loaded from the file and returned. -/// -/// # Returns -/// -/// An asynchronous Result containing the loaded SSH keypair if successful, or an error if any of the steps fail. -pub fn load_key(key_root: PathBuf) -> Result { - let key_path = PathBuf::from(&key_root).join("id_rsa"); - - if !Path::new(&key_root).exists() { - // create ssh directory if not exists - create_dir_all(&key_root).unwrap(); - } - - if !key_path.exists() { +pub fn load_key() -> KeyPair { + let ssh_key = read_secret("ssh_server_key").unwrap(); + if let Some(ssh_key) = ssh_key { + // load the keypair from the vault + let data = ssh_key.data.unwrap(); + let secret_key = data["secret_key"].as_str().unwrap().to_string(); + let keypair = SigningKey::from_pkcs8_pem(&secret_key).expect("parsing key err"); + KeyPair::Ed25519(keypair) + } else { // generate a keypair if not exists let keys = KeyPair::generate_ed25519().unwrap(); - if let KeyPair::Ed25519(inner_pair) = &keys { - // Handle other variants or provide a default behavior - inner_pair - .write_pkcs8_pem_file(key_path, LineEnding::CR) - .unwrap(); - inner_pair - .verifying_key() - .write_public_key_pem_file( - PathBuf::from(&key_root).join("id_rsa.pub"), - LineEnding::CR, - ) - .unwrap(); + let secret = serde_json::json!({ + "secret_key": *inner_pair.to_pkcs8_pem(LineEnding::CR).unwrap() + }) + .as_object() + .unwrap() + .clone(); + write_secret("ssh_server_key", Some(secret)).unwrap_or_else(|e| { + panic!("Failed to write ssh_server_key: {:?}", e); + }); } - Ok(keys) - } else { - // load the keypair from the file - let mut file = File::open(&key_path)?; - let mut pem_str = String::new(); - file.read_to_string(&mut pem_str).unwrap(); - let keypair = SigningKey::from_pkcs8_pem(&pem_str)?; - Ok(KeyPair::Ed25519(keypair)) + keys } } diff --git a/moon/src/app/(dashboard)/user/keys/add/page.tsx b/moon/src/app/(dashboard)/user/keys/add/page.tsx index 9a01909c9..0c2b834fe 100644 --- a/moon/src/app/(dashboard)/user/keys/add/page.tsx +++ b/moon/src/app/(dashboard)/user/keys/add/page.tsx @@ -9,6 +9,7 @@ import { useRouter } from 'next/navigation'; const { TextArea } = Input; export default function Settings() { + const [title, setTitle] = useState(''); const [ssh_key, setSSHKey] = useState(''); const [messageApi, contextHolder] = message.useMessage(); const router = useRouter(); @@ -20,13 +21,14 @@ export default function Settings() { }); }; - const save_ssh_key = async (ssh_key) => { + const save_ssh_key = async (title, ssh_key) => { const res = await fetch('/api/user/ssh', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ + title: title, ssh_key: ssh_key }), }); @@ -48,7 +50,7 @@ export default function Settings() { Title
- setTitle(e.target.value)} />
@@ -72,7 +74,7 @@ export default function Settings() { - + ) diff --git a/moon/src/app/(dashboard)/user/keys/page.tsx b/moon/src/app/(dashboard)/user/keys/page.tsx index de55c4365..6cc615786 100644 --- a/moon/src/app/(dashboard)/user/keys/page.tsx +++ b/moon/src/app/(dashboard)/user/keys/page.tsx @@ -14,6 +14,7 @@ interface KeyItem { ssh_key: string, created_at: string, finger: string, + title: string, } export default function KeysPage() { @@ -80,7 +81,13 @@ export default function KeysPage() { avatar={ } - title={`SHA256:${item.finger}`} + title={ + <> + {item.title} +
+ SHA256: {item.finger} + + } description={`Added on ${format(new Date(item.created_at), "MMM dd,yyyy")}`} /> diff --git a/sql/postgres/pg_20240910__init.sql b/sql/postgres/pg_20240912__init.sql similarity index 99% rename from sql/postgres/pg_20240910__init.sql rename to sql/postgres/pg_20240912__init.sql index ce97b2ec4..13875622b 100644 --- a/sql/postgres/pg_20240910__init.sql +++ b/sql/postgres/pg_20240912__init.sql @@ -291,6 +291,7 @@ CREATE TABLE IF NOT EXISTS "user" ( CREATE TABLE IF NOT EXISTS "ssh_keys" ( "id" BIGINT PRIMARY KEY, "user_id" BIGINT NOT NULL, + "title" TEXT NOT NULL, "ssh_key" TEXT NOT NULL, "finger" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL diff --git a/sql/sqlite/sqlite_20240910_init.sql b/sql/sqlite/sqlite_20240912_init.sql similarity index 99% rename from sql/sqlite/sqlite_20240910_init.sql rename to sql/sqlite/sqlite_20240912_init.sql index 32a2b2670..8e0e30e42 100644 --- a/sql/sqlite/sqlite_20240910_init.sql +++ b/sql/sqlite/sqlite_20240912_init.sql @@ -291,6 +291,7 @@ CREATE TABLE IF NOT EXISTS "user" ( CREATE TABLE IF NOT EXISTS "ssh_keys" ( "id" BIGINT PRIMARY KEY, "user_id" BIGINT NOT NULL, + "title" TEXT NOT NULL, "ssh_key" TEXT NOT NULL, "finger" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL