diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs index 24ef8702..60ddd5bf 100644 --- a/src/api2/config/remote.rs +++ b/src/api2/config/remote.rs @@ -63,29 +63,14 @@ pub fn list_remotes( name: { schema: REMOTE_ID_SCHEMA, }, - comment: { - optional: true, - schema: SINGLE_LINE_COMMENT_SCHEMA, - }, - host: { - schema: DNS_NAME_OR_IP_SCHEMA, - }, - port: { - description: "The (optional) port.", - type: u16, - optional: true, - default: 8007, - }, - "auth-id": { - type: Authid, + config: { + type: remote::RemoteConfig, + flatten: true, }, password: { + // We expect the plain password here (not base64 encoded) schema: remote::REMOTE_PASSWORD_SCHEMA, }, - fingerprint: { - optional: true, - schema: CERT_FINGERPRINT_SHA256_SCHEMA, - }, }, }, access: { @@ -93,23 +78,25 @@ pub fn list_remotes( }, )] /// Create new remote. -pub fn create_remote(password: String, param: Value) -> Result<(), Error> { +pub fn create_remote( + name: String, + config: remote::RemoteConfig, + password: String, +) -> Result<(), Error> { let _lock = open_backup_lockfile(remote::REMOTE_CFG_LOCKFILE, None, true)?; - let mut data = param; - data["password"] = Value::from(base64::encode(password.as_bytes())); - let remote: remote::Remote = serde_json::from_value(data)?; + let (mut section_config, _digest) = remote::config()?; - let (mut config, _digest) = remote::config()?; - - if config.sections.get(&remote.name).is_some() { - bail!("remote '{}' already exists.", remote.name); + if section_config.sections.get(&name).is_some() { + bail!("remote '{}' already exists.", name); } - config.set_data(&remote.name, "remote", &remote)?; + let remote = remote::Remote { name: name.clone(), config, password }; - remote::save_config(&config)?; + section_config.set_data(&name, "remote", &remote)?; + + remote::save_config(§ion_config)?; Ok(()) } @@ -160,31 +147,15 @@ pub enum DeletableProperty { name: { schema: REMOTE_ID_SCHEMA, }, - comment: { - optional: true, - schema: SINGLE_LINE_COMMENT_SCHEMA, - }, - host: { - optional: true, - schema: DNS_NAME_OR_IP_SCHEMA, - }, - port: { - description: "The (optional) port.", - type: u16, - optional: true, - }, - "auth-id": { - optional: true, - type: Authid, + update: { + type: remote::RemoteConfigUpdater, + flatten: true, }, password: { + // We expect the plain password here (not base64 encoded) optional: true, schema: remote::REMOTE_PASSWORD_SCHEMA, }, - fingerprint: { - optional: true, - schema: CERT_FINGERPRINT_SHA256_SCHEMA, - }, delete: { description: "List of properties to delete.", type: Array, @@ -204,15 +175,10 @@ pub enum DeletableProperty { }, )] /// Update remote configuration. -#[allow(clippy::too_many_arguments)] pub fn update_remote( name: String, - comment: Option, - host: Option, - port: Option, - auth_id: Option, + update: remote::RemoteConfigUpdater, password: Option, - fingerprint: Option, delete: Option>, digest: Option, ) -> Result<(), Error> { @@ -231,27 +197,27 @@ pub fn update_remote( if let Some(delete) = delete { for delete_prop in delete { match delete_prop { - DeletableProperty::comment => { data.comment = None; }, - DeletableProperty::fingerprint => { data.fingerprint = None; }, - DeletableProperty::port => { data.port = None; }, + DeletableProperty::comment => { data.config.comment = None; }, + DeletableProperty::fingerprint => { data.config.fingerprint = None; }, + DeletableProperty::port => { data.config.port = None; }, } } } - if let Some(comment) = comment { + if let Some(comment) = update.comment { let comment = comment.trim().to_string(); if comment.is_empty() { - data.comment = None; + data.config.comment = None; } else { - data.comment = Some(comment); + data.config.comment = Some(comment); } } - if let Some(host) = host { data.host = host; } - if port.is_some() { data.port = port; } - if let Some(auth_id) = auth_id { data.auth_id = auth_id; } + if let Some(host) = update.host { data.config.host = host; } + if update.port.is_some() { data.config.port = update.port; } + if let Some(auth_id) = update.auth_id { data.config.auth_id = auth_id; } if let Some(password) = password { data.password = password; } - if let Some(fingerprint) = fingerprint { data.fingerprint = Some(fingerprint); } + if update.fingerprint.is_some() { data.config.fingerprint = update.fingerprint; } config.set_data(&name, "remote", &data)?; @@ -312,16 +278,16 @@ pub fn delete_remote(name: String, digest: Option) -> Result<(), Error> /// Helper to get client for remote.cfg entry pub async fn remote_client(remote: remote::Remote) -> Result { - let options = HttpClientOptions::new_non_interactive(remote.password.clone(), remote.fingerprint.clone()); + let options = HttpClientOptions::new_non_interactive(remote.password.clone(), remote.config.fingerprint.clone()); let client = HttpClient::new( - &remote.host, - remote.port.unwrap_or(8007), - &remote.auth_id, + &remote.config.host, + remote.config.port.unwrap_or(8007), + &remote.config.auth_id, options)?; let _auth_info = client.login() // make sure we can auth .await - .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?; + .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.config.host, err))?; Ok(client) } diff --git a/src/api2/pull.rs b/src/api2/pull.rs index 4893c9fb..cf4e2ef9 100644 --- a/src/api2/pull.rs +++ b/src/api2/pull.rs @@ -53,7 +53,12 @@ pub async fn get_pull_parameters( let (remote_config, _digest) = remote::config()?; let remote: remote::Remote = remote_config.lookup("remote", remote)?; - let src_repo = BackupRepository::new(Some(remote.auth_id.clone()), Some(remote.host.clone()), remote.port, remote_store.to_string()); + let src_repo = BackupRepository::new( + Some(remote.config.auth_id.clone()), + Some(remote.config.host.clone()), + remote.config.port, + remote_store.to_string(), + ); let client = crate::api2::config::remote::remote_client(remote).await?; diff --git a/src/config/remote.rs b/src/config/remote.rs index 86fe7b6e..3e6bc916 100644 --- a/src/config/remote.rs +++ b/src/config/remote.rs @@ -25,11 +25,14 @@ pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth t .max_length(1024) .schema(); +pub const REMOTE_PASSWORD_BASE64_SCHEMA: Schema = StringSchema::new("Password or auth token for remote host (stored as base64 string).") + .format(&PASSWORD_FORMAT) + .min_length(1) + .max_length(1024) + .schema(); + #[api( properties: { - name: { - schema: REMOTE_ID_SCHEMA, - }, comment: { optional: true, schema: SINGLE_LINE_COMMENT_SCHEMA, @@ -45,36 +48,55 @@ pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth t "auth-id": { type: Authid, }, - password: { - schema: REMOTE_PASSWORD_SCHEMA, - }, fingerprint: { optional: true, schema: CERT_FINGERPRINT_SHA256_SCHEMA, }, - } + }, )] -#[derive(Serialize,Deserialize)] +#[derive(Serialize,Deserialize,Updater)] #[serde(rename_all = "kebab-case")] -/// Remote properties. -pub struct Remote { - pub name: String, +/// Remote configuration properties. +pub struct RemoteConfig { #[serde(skip_serializing_if="Option::is_none")] pub comment: Option, pub host: String, #[serde(skip_serializing_if="Option::is_none")] pub port: Option, pub auth_id: Authid, - #[serde(skip_serializing_if="String::is_empty")] - #[serde(with = "proxmox::tools::serde::string_as_base64")] - pub password: String, #[serde(skip_serializing_if="Option::is_none")] pub fingerprint: Option, } +#[api( + properties: { + name: { + schema: REMOTE_ID_SCHEMA, + }, + config: { + type: RemoteConfig, + }, + password: { + schema: REMOTE_PASSWORD_BASE64_SCHEMA, + }, + }, +)] +#[derive(Serialize,Deserialize)] +#[serde(rename_all = "kebab-case")] +/// Remote properties. +pub struct Remote { + pub name: String, + // Note: The stored password is base64 encoded + #[serde(skip_serializing_if="String::is_empty")] + #[serde(with = "proxmox::tools::serde::string_as_base64")] + pub password: String, + #[serde(flatten)] + pub config: RemoteConfig, +} + fn init() -> SectionConfig { let obj_schema = match Remote::API_SCHEMA { - Schema::Object(ref obj_schema) => obj_schema, + Schema::AllOf(ref allof_schema) => allof_schema, _ => unreachable!(), };