Generate RSA key for instance actor automatically on the first run
This commit is contained in:
parent
a1af46f566
commit
fac0172159
8 changed files with 64 additions and 33 deletions
|
@ -69,13 +69,7 @@ psql -h localhost -p 55432 -U mitra mitra
|
||||||
|
|
||||||
### Run web service
|
### Run web service
|
||||||
|
|
||||||
Generate instance key:
|
Create config file, adjust settings if needed:
|
||||||
|
|
||||||
```
|
|
||||||
cargo run --bin mitractl generate-rsa-key
|
|
||||||
```
|
|
||||||
|
|
||||||
Create config file, set `instance_rsa_key`, adjust other settings if needed:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cp config.yaml.example config.yaml
|
cp config.yaml.example config.yaml
|
||||||
|
|
|
@ -11,8 +11,6 @@ instance_short_description: my fedi instance
|
||||||
# Long description can contain markdown syntax
|
# Long description can contain markdown syntax
|
||||||
instance_description: my fedi instance
|
instance_description: my fedi instance
|
||||||
|
|
||||||
# RSA private key can be generated with `mitractl generate-rsa-key`
|
|
||||||
instance_rsa_key: null
|
|
||||||
registrations_open: false
|
registrations_open: false
|
||||||
# EIP-4361 login message
|
# EIP-4361 login message
|
||||||
login_message: 'Do not sign this message on other sites!'
|
login_message: 'Do not sign this message on other sites!'
|
||||||
|
|
|
@ -357,7 +357,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_local_actor() {
|
fn test_local_actor() {
|
||||||
let private_key = generate_weak_private_key().unwrap();
|
let private_key = generate_weak_private_key().unwrap();
|
||||||
let private_key_pem = serialize_private_key(private_key).unwrap();
|
let private_key_pem = serialize_private_key(&private_key).unwrap();
|
||||||
let profile = DbActorProfile {
|
let profile = DbActorProfile {
|
||||||
username: "testuser".to_string(),
|
username: "testuser".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -42,7 +42,7 @@ struct GenerateRsaKey;
|
||||||
impl GenerateRsaKey {
|
impl GenerateRsaKey {
|
||||||
fn execute(&self) -> () {
|
fn execute(&self) -> () {
|
||||||
let private_key = generate_private_key().unwrap();
|
let private_key = generate_private_key().unwrap();
|
||||||
let private_key_str = serialize_private_key(private_key).unwrap();
|
let private_key_str = serialize_private_key(&private_key).unwrap();
|
||||||
println!("{}", private_key_str);
|
println!("{}", private_key_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use log::{Level as LogLevel};
|
use log::{Level as LogLevel};
|
||||||
|
@ -10,7 +10,12 @@ use crate::activitypub::views::get_instance_actor_url;
|
||||||
use crate::errors::ConversionError;
|
use crate::errors::ConversionError;
|
||||||
use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError};
|
use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError};
|
||||||
use crate::models::profiles::currencies::Currency;
|
use crate::models::profiles::currencies::Currency;
|
||||||
use crate::utils::crypto::deserialize_private_key;
|
use crate::utils::crypto::{
|
||||||
|
deserialize_private_key,
|
||||||
|
generate_private_key,
|
||||||
|
serialize_private_key,
|
||||||
|
};
|
||||||
|
use crate::utils::files::{set_file_permissions, write_file};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Environment {
|
pub enum Environment {
|
||||||
|
@ -115,7 +120,9 @@ pub struct Config {
|
||||||
pub instance_title: String,
|
pub instance_title: String,
|
||||||
pub instance_short_description: String,
|
pub instance_short_description: String,
|
||||||
pub instance_description: String,
|
pub instance_description: String,
|
||||||
instance_rsa_key: String,
|
|
||||||
|
#[serde(skip)]
|
||||||
|
instance_rsa_key: Option<RsaPrivateKey>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub registrations_open: bool, // default is false
|
pub registrations_open: bool, // default is false
|
||||||
|
@ -150,15 +157,11 @@ impl Config {
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_instance_rsa_key(&self) -> Result<RsaPrivateKey, rsa::pkcs8::Error> {
|
|
||||||
deserialize_private_key(&self.instance_rsa_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance(&self) -> Instance {
|
pub fn instance(&self) -> Instance {
|
||||||
Instance {
|
Instance {
|
||||||
_url: self.try_instance_url().unwrap(),
|
_url: self.try_instance_url().unwrap(),
|
||||||
_version: self.version.clone(),
|
_version: self.version.clone(),
|
||||||
actor_key: self.try_instance_rsa_key().unwrap(),
|
actor_key: self.instance_rsa_key.clone().unwrap(),
|
||||||
is_private: matches!(self.environment, Environment::Development),
|
is_private: matches!(self.environment, Environment::Development),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,6 +215,28 @@ impl Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates new instance RSA key or returns existing key
|
||||||
|
fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
|
||||||
|
let private_key_path = storage_dir.join("instance_rsa_key");
|
||||||
|
if private_key_path.exists() {
|
||||||
|
let private_key_str = std::fs::read_to_string(&private_key_path)
|
||||||
|
.expect("failed to read instance RSA key");
|
||||||
|
let private_key = deserialize_private_key(&private_key_str)
|
||||||
|
.expect("failed to read instance RSA key");
|
||||||
|
private_key
|
||||||
|
} else {
|
||||||
|
let private_key = generate_private_key()
|
||||||
|
.expect("failed to generate RSA key");
|
||||||
|
let private_key_str = serialize_private_key(&private_key)
|
||||||
|
.expect("failed to serialize RSA key");
|
||||||
|
write_file(private_key_str.as_bytes(), &private_key_path)
|
||||||
|
.expect("failed to write instance RSA key");
|
||||||
|
set_file_permissions(&private_key_path, 0o600)
|
||||||
|
.expect("failed to set permissions on RSA key file");
|
||||||
|
private_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_config() -> Config {
|
pub fn parse_config() -> Config {
|
||||||
let env = parse_env();
|
let env = parse_env();
|
||||||
let config_yaml = std::fs::read_to_string(env.config_path)
|
let config_yaml = std::fs::read_to_string(env.config_path)
|
||||||
|
@ -220,36 +245,38 @@ pub fn parse_config() -> Config {
|
||||||
.expect("invalid yaml data");
|
.expect("invalid yaml data");
|
||||||
// Override environment parameter in config if env variable is set
|
// Override environment parameter in config if env variable is set
|
||||||
config.environment = env.environment.unwrap_or(config.environment);
|
config.environment = env.environment.unwrap_or(config.environment);
|
||||||
// Set_version
|
// Set version
|
||||||
config.version = env.crate_version;
|
config.version = env.crate_version;
|
||||||
// Validate config
|
// Validate config
|
||||||
if !config.storage_dir.exists() {
|
if !config.storage_dir.exists() {
|
||||||
panic!("storage directory does not exist");
|
panic!("storage directory does not exist");
|
||||||
};
|
};
|
||||||
|
config.try_instance_url().expect("invalid instance URI");
|
||||||
if let Some(blockchain_config) = config.blockchain.as_ref() {
|
if let Some(blockchain_config) = config.blockchain.as_ref() {
|
||||||
blockchain_config.try_ethereum_chain_id().unwrap();
|
blockchain_config.try_ethereum_chain_id().unwrap();
|
||||||
if !blockchain_config.contract_dir.exists() {
|
if !blockchain_config.contract_dir.exists() {
|
||||||
panic!("contract directory does not exist");
|
panic!("contract directory does not exist");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.try_instance_url().expect("invalid instance URI");
|
|
||||||
config.try_instance_rsa_key().expect("invalid instance RSA key");
|
|
||||||
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
|
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
|
||||||
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
|
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Insert instance RSA key
|
||||||
|
config.instance_rsa_key = Some(read_instance_rsa_key(&config.storage_dir));
|
||||||
|
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use rand::rngs::OsRng;
|
use crate::utils::crypto::generate_weak_private_key;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_instance_url_https_dns() {
|
fn test_instance_url_https_dns() {
|
||||||
let instance_url = Url::parse("https://example.com/").unwrap();
|
let instance_url = Url::parse("https://example.com/").unwrap();
|
||||||
let instance_rsa_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap();
|
let instance_rsa_key = generate_weak_private_key().unwrap();
|
||||||
let instance = Instance {
|
let instance = Instance {
|
||||||
_url: instance_url,
|
_url: instance_url,
|
||||||
_version: "1.0.0".to_string(),
|
_version: "1.0.0".to_string(),
|
||||||
|
@ -265,7 +292,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_instance_url_http_ipv4() {
|
fn test_instance_url_http_ipv4() {
|
||||||
let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap();
|
let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap();
|
||||||
let instance_rsa_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap();
|
let instance_rsa_key = generate_weak_private_key().unwrap();
|
||||||
let instance = Instance {
|
let instance = Instance {
|
||||||
_url: instance_url,
|
_url: instance_url,
|
||||||
_version: "1.0.0".to_string(),
|
_version: "1.0.0".to_string(),
|
||||||
|
|
|
@ -123,7 +123,7 @@ pub async fn create_account(
|
||||||
Ok(Ok(private_key)) => private_key,
|
Ok(Ok(private_key)) => private_key,
|
||||||
_ => return Err(HttpError::InternalError),
|
_ => return Err(HttpError::InternalError),
|
||||||
};
|
};
|
||||||
let private_key_pem = serialize_private_key(private_key)
|
let private_key_pem = serialize_private_key(&private_key)
|
||||||
.map_err(|_| HttpError::InternalError)?;
|
.map_err(|_| HttpError::InternalError)?;
|
||||||
|
|
||||||
let AccountCreateData { username, invite_code, .. } =
|
let AccountCreateData { username, invite_code, .. } =
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub fn generate_weak_private_key() -> Result<RsaPrivateKey, rsa::errors::Error>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_private_key(
|
pub fn serialize_private_key(
|
||||||
private_key: RsaPrivateKey,
|
private_key: &RsaPrivateKey,
|
||||||
) -> Result<String, rsa::pkcs8::Error> {
|
) -> Result<String, rsa::pkcs8::Error> {
|
||||||
private_key.to_pkcs8_pem().map(|val| val.to_string())
|
private_key.to_pkcs8_pem().map(|val| val.to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
use std::fs::{remove_file, File};
|
use std::fs::{
|
||||||
|
remove_file,
|
||||||
|
set_permissions,
|
||||||
|
File,
|
||||||
|
Permissions,
|
||||||
|
};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use mime_guess::get_mime_extensions_str;
|
use mime_guess::get_mime_extensions_str;
|
||||||
|
@ -36,9 +42,15 @@ fn get_file_name(data: &[u8], media_type: Option<&str>) -> String {
|
||||||
file_name
|
file_name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_file(data: Vec<u8>, file_path: &Path) -> Result<(), FileError> {
|
pub fn write_file(data: &[u8], file_path: &Path) -> Result<(), FileError> {
|
||||||
let mut file = File::create(file_path)?;
|
let mut file = File::create(file_path)?;
|
||||||
file.write_all(&data)?;
|
file.write_all(data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_file_permissions(file_path: &Path, mode: u32) -> Result<(), FileError> {
|
||||||
|
let permissions = Permissions::from_mode(mode);
|
||||||
|
set_permissions(file_path, permissions)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +61,7 @@ pub fn save_file(
|
||||||
let media_type = sniff_media_type(&data);
|
let media_type = sniff_media_type(&data);
|
||||||
let file_name = get_file_name(&data, media_type.as_deref());
|
let file_name = get_file_name(&data, media_type.as_deref());
|
||||||
let file_path = output_dir.join(&file_name);
|
let file_path = output_dir.join(&file_name);
|
||||||
write_file(data, &file_path)?;
|
write_file(&data, &file_path)?;
|
||||||
Ok((file_name, media_type))
|
Ok((file_name, media_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +73,7 @@ pub fn save_b64_file(
|
||||||
let media_type = sniff_media_type(&data);
|
let media_type = sniff_media_type(&data);
|
||||||
let file_name = get_file_name(&data, media_type.as_deref());
|
let file_name = get_file_name(&data, media_type.as_deref());
|
||||||
let file_path = output_dir.join(&file_name);
|
let file_path = output_dir.join(&file_name);
|
||||||
write_file(data, &file_path)?;
|
write_file(&data, &file_path)?;
|
||||||
Ok((file_name, media_type))
|
Ok((file_name, media_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +90,7 @@ pub fn save_validated_b64_file(
|
||||||
}
|
}
|
||||||
let file_name = get_file_name(&data, Some(&media_type));
|
let file_name = get_file_name(&data, Some(&media_type));
|
||||||
let file_path = output_dir.join(&file_name);
|
let file_path = output_dir.join(&file_name);
|
||||||
write_file(data, &file_path)?;
|
write_file(&data, &file_path)?;
|
||||||
Ok((file_name, media_type))
|
Ok((file_name, media_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue