|
|
|
@@ -40,7 +40,14 @@ impl ARN {
|
|
|
|
|
|
|
|
|
|
impl Display for ARN {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "arn:rustfs:{}:{}:{}:{}", self.arn_type, self.region, self.id, self.bucket)
|
|
|
|
|
// The `minio` partition is deliberate: madmin-go's ParseARN
|
|
|
|
|
// hard-rejects any other partition, so native mc/madmin tooling can
|
|
|
|
|
// only decode remote-target ARNs minted in this form (backlog#1675
|
|
|
|
|
// P1-7). Legacy `arn:rustfs:` ARNs persisted by older releases stay
|
|
|
|
|
// readable via the FromStr whitelist below; runtime matching between
|
|
|
|
|
// targets and replication rules is by full-string equality, so mixed
|
|
|
|
|
// partitions coexist safely.
|
|
|
|
|
write!(f, "arn:minio:{}:{}:{}:{}", self.arn_type, self.region, self.id, self.bucket)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -48,7 +55,12 @@ impl FromStr for ARN {
|
|
|
|
|
type Err = std::io::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
if !s.starts_with("arn:rustfs:") {
|
|
|
|
|
// Partition whitelist, not just an `arn:` check: `BucketTargetType::
|
|
|
|
|
// from_str(...).unwrap_or_default()` below never fails, so this is
|
|
|
|
|
// the only structural gate rejecting foreign ARNs. `arn:rustfs:` is
|
|
|
|
|
// the legacy partition and must stay accepted forever (persisted
|
|
|
|
|
// bucket-targets.json / replication configs from older releases).
|
|
|
|
|
if !s.starts_with("arn:minio:") && !s.starts_with("arn:rustfs:") {
|
|
|
|
|
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid ARN format"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -101,14 +113,50 @@ mod tests {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// RustFS commonly generates ARNs with an empty region:
|
|
|
|
|
/// `arn:rustfs:replication::<deployment_id>:<bucket>`.
|
|
|
|
|
/// `arn:minio:replication::<deployment_id>:<bucket>`.
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_str_handles_empty_region_segment() {
|
|
|
|
|
let parsed = ARN::from_str("arn:rustfs:replication::depl-123:bucket-a").expect("valid ARN must parse");
|
|
|
|
|
let parsed = ARN::from_str("arn:minio:replication::depl-123:bucket-a").expect("valid ARN must parse");
|
|
|
|
|
|
|
|
|
|
assert_eq!(parsed.arn_type, BucketTargetType::ReplicationService);
|
|
|
|
|
assert_eq!(parsed.region, "", "region segment is empty in this form");
|
|
|
|
|
assert_eq!(parsed.id, "depl-123");
|
|
|
|
|
assert_eq!(parsed.bucket, "bucket-a");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// madmin-go's `ParseARN` hard-rejects anything that does not start with
|
|
|
|
|
/// `arn:minio:`, so generated ARNs must use the `minio` partition or the
|
|
|
|
|
/// native mc/madmin tooling cannot decode remote-target listings.
|
|
|
|
|
#[test]
|
|
|
|
|
fn display_emits_minio_partition() {
|
|
|
|
|
let arn = ARN::new(
|
|
|
|
|
BucketTargetType::ReplicationService,
|
|
|
|
|
"depl-123".to_string(),
|
|
|
|
|
String::new(),
|
|
|
|
|
"bucket-a".to_string(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(arn.to_string(), "arn:minio:replication::depl-123:bucket-a");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Persisted bucket-targets.json files from older RustFS releases carry
|
|
|
|
|
/// `arn:rustfs:` ARNs; the legacy partition must stay parseable forever.
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_str_accepts_legacy_rustfs_partition() {
|
|
|
|
|
let parsed = ARN::from_str("arn:rustfs:replication:us-east-1:depl-123:bucket-a").expect("legacy ARN must parse");
|
|
|
|
|
|
|
|
|
|
assert_eq!(parsed.arn_type, BucketTargetType::ReplicationService);
|
|
|
|
|
assert_eq!(parsed.region, "us-east-1");
|
|
|
|
|
assert_eq!(parsed.id, "depl-123");
|
|
|
|
|
assert_eq!(parsed.bucket, "bucket-a");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The partition whitelist is the only structural gate: `BucketTargetType::
|
|
|
|
|
/// from_str(...).unwrap_or_default()` never fails, so any 6-segment string
|
|
|
|
|
/// would otherwise parse as `type=None`.
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_str_rejects_unknown_partition() {
|
|
|
|
|
assert!(ARN::from_str("arn:aws:replication::depl-123:bucket-a").is_err());
|
|
|
|
|
assert!(ARN::from_str("not-an-arn").is_err());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|