github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/replication/replication.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package replication 19 20 import ( 21 "encoding/xml" 22 "io" 23 "sort" 24 "strconv" 25 "strings" 26 ) 27 28 var ( 29 errReplicationTooManyRules = Errorf("Replication configuration allows a maximum of 1000 rules") 30 errReplicationNoRule = Errorf("Replication configuration should have at least one rule") 31 errReplicationUniquePriority = Errorf("Replication configuration has duplicate priority") 32 errRoleArnMissingLegacy = Errorf("Missing required parameter `Role` in ReplicationConfiguration") 33 errDestinationArnMissing = Errorf("Missing required parameter `Destination` in Replication rule") 34 errInvalidSourceSelectionCriteria = Errorf("Invalid ReplicaModification status") 35 errRoleArnPresentForMultipleTargets = Errorf("`Role` should be empty in ReplicationConfiguration for multiple targets") 36 ) 37 38 // Config - replication configuration specified in 39 // https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html 40 type Config struct { 41 XMLName xml.Name `xml:"ReplicationConfiguration" json:"-"` 42 Rules []Rule `xml:"Rule" json:"Rules"` 43 // RoleArn is being reused for MinIO replication ARN 44 RoleArn string `xml:"Role" json:"Role"` 45 } 46 47 // Maximum 2MiB size per replication config. 48 const maxReplicationConfigSize = 2 << 20 49 50 // ParseConfig parses ReplicationConfiguration from xml 51 func ParseConfig(reader io.Reader) (*Config, error) { 52 config := Config{} 53 if err := xml.NewDecoder(io.LimitReader(reader, maxReplicationConfigSize)).Decode(&config); err != nil { 54 return nil, err 55 } 56 // By default, set replica modification to enabled if unset. 57 for i := range config.Rules { 58 if len(config.Rules[i].SourceSelectionCriteria.ReplicaModifications.Status) == 0 { 59 config.Rules[i].SourceSelectionCriteria = SourceSelectionCriteria{ 60 ReplicaModifications: ReplicaModifications{ 61 Status: Enabled, 62 }, 63 } 64 } 65 // Default DeleteReplication to disabled if unset. 66 if len(config.Rules[i].DeleteReplication.Status) == 0 { 67 config.Rules[i].DeleteReplication = DeleteReplication{ 68 Status: Disabled, 69 } 70 } 71 } 72 return &config, nil 73 } 74 75 // Validate - validates the replication configuration 76 func (c Config) Validate(bucket string, sameTarget bool) error { 77 // replication config can't have more than 1000 rules 78 if len(c.Rules) > 1000 { 79 return errReplicationTooManyRules 80 } 81 // replication config should have at least one rule 82 if len(c.Rules) == 0 { 83 return errReplicationNoRule 84 } 85 86 // Validate all the rules in the replication config 87 targetMap := make(map[string]struct{}) 88 priorityMap := make(map[string]struct{}) 89 var legacyArn bool 90 for _, r := range c.Rules { 91 if _, ok := targetMap[r.Destination.Bucket]; !ok { 92 targetMap[r.Destination.Bucket] = struct{}{} 93 } 94 if err := r.Validate(bucket, sameTarget); err != nil { 95 return err 96 } 97 if _, ok := priorityMap[strconv.Itoa(r.Priority)]; ok { 98 return errReplicationUniquePriority 99 } 100 priorityMap[strconv.Itoa(r.Priority)] = struct{}{} 101 102 if r.Destination.LegacyArn() { 103 legacyArn = true 104 } 105 if c.RoleArn == "" && !r.Destination.TargetArn() { 106 return errDestinationArnMissing 107 } 108 } 109 // disallow combining old replication configuration which used RoleArn as target ARN with multiple 110 // destination replication 111 if c.RoleArn != "" && len(targetMap) > 1 { 112 return errRoleArnPresentForMultipleTargets 113 } 114 // validate RoleArn if destination used legacy ARN format. 115 if c.RoleArn == "" && legacyArn { 116 return errRoleArnMissingLegacy 117 } 118 return nil 119 } 120 121 // Types of replication 122 const ( 123 UnsetReplicationType Type = 0 + iota 124 ObjectReplicationType 125 DeleteReplicationType 126 MetadataReplicationType 127 HealReplicationType 128 ExistingObjectReplicationType 129 ResyncReplicationType 130 AllReplicationType 131 ) 132 133 // Valid returns true if replication type is set 134 func (t Type) Valid() bool { 135 return t > 0 136 } 137 138 // IsDataReplication returns true if content being replicated 139 func (t Type) IsDataReplication() bool { 140 switch t { 141 case ObjectReplicationType, HealReplicationType, ExistingObjectReplicationType: 142 return true 143 } 144 return false 145 } 146 147 // ObjectOpts provides information to deduce whether replication 148 // can be triggered on the resultant object. 149 type ObjectOpts struct { 150 Name string 151 UserTags string 152 VersionID string 153 DeleteMarker bool 154 SSEC bool 155 OpType Type 156 Replica bool 157 ExistingObject bool 158 TargetArn string 159 } 160 161 // HasExistingObjectReplication returns true if any of the rule returns 'ExistingObjects' replication. 162 func (c Config) HasExistingObjectReplication(arn string) (hasARN, isEnabled bool) { 163 for _, rule := range c.Rules { 164 if rule.Destination.ARN == arn || c.RoleArn == arn { 165 if !hasARN { 166 hasARN = true 167 } 168 if rule.ExistingObjectReplication.Status == Enabled { 169 return true, true 170 } 171 } 172 } 173 return hasARN, false 174 } 175 176 // FilterActionableRules returns the rules actions that need to be executed 177 // after evaluating prefix/tag filtering 178 func (c Config) FilterActionableRules(obj ObjectOpts) []Rule { 179 if obj.Name == "" && !(obj.OpType == ResyncReplicationType || obj.OpType == AllReplicationType) { 180 return nil 181 } 182 var rules []Rule 183 for _, rule := range c.Rules { 184 if rule.Status == Disabled { 185 continue 186 } 187 188 if obj.TargetArn != "" && rule.Destination.ARN != obj.TargetArn && c.RoleArn != obj.TargetArn { 189 continue 190 } 191 // Ignore other object level and prefix filters for resyncing target/listing bucket targets 192 if obj.OpType == ResyncReplicationType || obj.OpType == AllReplicationType { 193 rules = append(rules, rule) 194 continue 195 } 196 if obj.ExistingObject && rule.ExistingObjectReplication.Status == Disabled { 197 continue 198 } 199 if !strings.HasPrefix(obj.Name, rule.Prefix()) { 200 continue 201 } 202 if rule.Filter.TestTags(obj.UserTags) { 203 rules = append(rules, rule) 204 } 205 } 206 sort.Slice(rules, func(i, j int) bool { 207 return rules[i].Priority > rules[j].Priority && rules[i].Destination.String() == rules[j].Destination.String() 208 }) 209 210 return rules 211 } 212 213 // GetDestination returns destination bucket and storage class. 214 func (c Config) GetDestination() Destination { 215 if len(c.Rules) > 0 { 216 return c.Rules[0].Destination 217 } 218 return Destination{} 219 } 220 221 // Replicate returns true if the object should be replicated. 222 func (c Config) Replicate(obj ObjectOpts) bool { 223 for _, rule := range c.FilterActionableRules(obj) { 224 if rule.Status == Disabled { 225 continue 226 } 227 if obj.ExistingObject && rule.ExistingObjectReplication.Status == Disabled { 228 return false 229 } 230 if obj.OpType == DeleteReplicationType { 231 switch { 232 case obj.VersionID != "": 233 // check MinIO extension for versioned deletes 234 return rule.DeleteReplication.Status == Enabled 235 default: 236 return rule.DeleteMarkerReplication.Status == Enabled 237 } 238 } // regular object/metadata replication 239 return rule.MetadataReplicate(obj) 240 } 241 return false 242 } 243 244 // HasActiveRules - returns whether replication policy has active rules 245 // Optionally a prefix can be supplied. 246 // If recursive is specified the function will also return true if any level below the 247 // prefix has active rules. If no prefix is specified recursive is effectively true. 248 func (c Config) HasActiveRules(prefix string, recursive bool) bool { 249 if len(c.Rules) == 0 { 250 return false 251 } 252 for _, rule := range c.Rules { 253 if rule.Status == Disabled { 254 continue 255 } 256 if len(prefix) > 0 && len(rule.Filter.Prefix) > 0 { 257 // incoming prefix must be in rule prefix 258 if !recursive && !strings.HasPrefix(prefix, rule.Filter.Prefix) { 259 continue 260 } 261 // If recursive, we can skip this rule if it doesn't match the tested prefix or level below prefix 262 // does not match 263 if recursive && !strings.HasPrefix(rule.Prefix(), prefix) && !strings.HasPrefix(prefix, rule.Prefix()) { 264 continue 265 } 266 } 267 return true 268 } 269 return false 270 } 271 272 // FilterTargetArns returns a slice of distinct target arns in the config 273 func (c Config) FilterTargetArns(obj ObjectOpts) []string { 274 var arns []string 275 276 tgtsMap := make(map[string]struct{}) 277 rules := c.FilterActionableRules(obj) 278 for _, rule := range rules { 279 if rule.Status == Disabled { 280 continue 281 } 282 if c.RoleArn != "" { 283 arns = append(arns, c.RoleArn) // use legacy RoleArn if present 284 return arns 285 } 286 if _, ok := tgtsMap[rule.Destination.ARN]; !ok { 287 tgtsMap[rule.Destination.ARN] = struct{}{} 288 } 289 } 290 for k := range tgtsMap { 291 arns = append(arns, k) 292 } 293 return arns 294 }