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  }