From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: <pbs-devel-bounces@lists.proxmox.com> Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 76AD71FF183 for <inbox@lore.proxmox.com>; Mon, 19 May 2025 13:47:10 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 303A1828A; Mon, 19 May 2025 13:47:06 +0200 (CEST) From: Christian Ebner <c.ebner@proxmox.com> To: pbs-devel@lists.proxmox.com Date: Mon, 19 May 2025 13:46:02 +0200 Message-Id: <20250519114640.303640-2-c.ebner@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250519114640.303640-1-c.ebner@proxmox.com> References: <20250519114640.303640-1-c.ebner@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.028 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [s3.rs, lib.rs] Subject: [pbs-devel] [RFC proxmox 1/2] pbs-api-types: add types for S3 client configs and secrets X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion <pbs-devel.lists.proxmox.com> List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pbs-devel>, <mailto:pbs-devel-request@lists.proxmox.com?subject=unsubscribe> List-Archive: <http://lists.proxmox.com/pipermail/pbs-devel/> List-Post: <mailto:pbs-devel@lists.proxmox.com> List-Help: <mailto:pbs-devel-request@lists.proxmox.com?subject=help> List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel>, <mailto:pbs-devel-request@lists.proxmox.com?subject=subscribe> Reply-To: Proxmox Backup Server development discussion <pbs-devel@lists.proxmox.com> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pbs-devel-bounces@lists.proxmox.com Sender: "pbs-devel" <pbs-devel-bounces@lists.proxmox.com> Adds the new config types `S3ClientConfig` and `S3ClientSecret` to configure datastore backends using an S3 compatible object store. Secrets are stored as different config to never be returned on api calls, only allowing to set/update the values. Use a different name (`secrets_id`) for the unique identifier in case of the secrets type, although the same id should be used for storing and lookup. By this, clashing of property names when using flattened types as api parameters is avoided. Signed-off-by: Christian Ebner <c.ebner@proxmox.com> --- pbs-api-types/src/lib.rs | 3 + pbs-api-types/src/s3.rs | 138 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 pbs-api-types/src/s3.rs diff --git a/pbs-api-types/src/lib.rs b/pbs-api-types/src/lib.rs index 99ec7961..7a5ea11d 100644 --- a/pbs-api-types/src/lib.rs +++ b/pbs-api-types/src/lib.rs @@ -147,6 +147,9 @@ pub use remote::*; mod pathpatterns; pub use pathpatterns::*; +mod s3; +pub use s3::*; + mod tape; pub use tape::*; diff --git a/pbs-api-types/src/s3.rs b/pbs-api-types/src/s3.rs new file mode 100644 index 00000000..40c502ba --- /dev/null +++ b/pbs-api-types/src/s3.rs @@ -0,0 +1,138 @@ +use anyhow::bail; +use serde::{Deserialize, Serialize}; + +use proxmox_schema::api_types::{ + CERT_FINGERPRINT_SHA256_SCHEMA, DNS_NAME_OR_IP_SCHEMA, SAFE_ID_FORMAT, +}; +use proxmox_schema::{api, const_regex, ApiStringFormat, Schema, StringSchema, Updater}; + +#[rustfmt::skip] +pub const S3_BUCKET_NAME_REGEX_STR: &str = r"^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$"; + +const_regex! { + /// Regex to match S3 bucket names. + /// + /// Be as strict as possible following the rules as described here: + /// https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html#general-purpose-bucket-names + pub S3_BUCKET_NAME_REGEX = r"^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$"; + /// Regex to match S3 regions. + pub S3_REGION_REGEX = r"^[a-z]{2}\-[a-z]{4,}\-[0-9]$"; +} + +pub const S3_REGION_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&S3_REGION_REGEX); + +pub const S3_CLIENT_ID_SCHEMA: Schema = + StringSchema::new("Unique ID to identify s3 client config.") + .format(&SAFE_ID_FORMAT) + .min_length(3) + .max_length(32) + .schema(); + +pub const S3_REGION_SCHEMA: Schema = StringSchema::new("Region to access S3 object store.") + .format(&S3_REGION_FORMAT) + .min_length(3) + .max_length(32) + .schema(); + +pub const S3_BUCKET_NAME_SCHEMA: Schema = StringSchema::new("Bucket name for S3 object store.") + .format(&ApiStringFormat::VerifyFn(|bucket_name| { + if !(S3_BUCKET_NAME_REGEX.regex_obj)().is_match(bucket_name) { + bail!("Bucket name does not match the regex pattern"); + } + + // Exclude pre- and postfixes described here: + // https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html#general-purpose-bucket-names + let forbidden_prefixes = ["xn--", "sthree-", "amzn-s3-demo-"]; + for prefix in forbidden_prefixes { + if bucket_name.starts_with(prefix) { + bail!("Bucket name cannot start with '{prefix}'"); + } + } + + let forbidden_postfixes = ["--ol-s3", ".mrap", "--x-s3"]; + for postfix in forbidden_postfixes { + if bucket_name.ends_with(postfix) { + bail!("Bucket name cannot end with '{postfix}'"); + } + } + + Ok(()) + })) + .min_length(3) + .max_length(63) + .schema(); + +#[api( + properties: { + id: { + schema: S3_CLIENT_ID_SCHEMA, + }, + host: { + schema: DNS_NAME_OR_IP_SCHEMA, + }, + bucket: { + schema: S3_BUCKET_NAME_SCHEMA, + }, + port: { + type: u16, + description: "Port to access S3 object store.", + optional: true, + }, + region: { + schema: S3_REGION_SCHEMA, + optional: true, + }, + fingerprint: { + schema: CERT_FINGERPRINT_SHA256_SCHEMA, + optional: true, + }, + "access-key": { + type: String, + description: "Access key for S3 object store.", + }, + } +)] +#[derive(Serialize, Deserialize, Updater, Clone, PartialEq)] +#[serde(rename_all = "kebab-case")] +/// S3 client configuration properties. +pub struct S3ClientConfig { + #[updater(skip)] + pub id: String, + pub host: String, + pub bucket: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub port: Option<u16>, + #[serde(skip_serializing_if = "Option::is_none")] + pub region: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub fingerprint: Option<String>, + pub access_key: String, +} + +impl S3ClientConfig { + pub fn acl_path(&self) -> Vec<&str> { + // Needs permissions on root path + Vec::new() + } +} + +#[api( + properties: { + "secrets-id": { + type: String, + description: "Unique ID to identify s3 client secret config.", + }, + "secret-key": { + type: String, + description: "Secret key for S3 object store.", + }, + } +)] +#[derive(Serialize, Deserialize, Updater, Clone, PartialEq)] +#[serde(rename_all = "kebab-case")] +/// S3 client secrets configuration properties. +pub struct S3ClientSecretsConfig { + #[updater(skip)] + pub secrets_id: String, + pub secret_key: String, +} -- 2.39.5 _______________________________________________ pbs-devel mailing list pbs-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel