storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/object/lock/lock.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package lock
    18  
    19  import (
    20  	"context"
    21  	"encoding/xml"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/beevik/ntp"
    30  
    31  	"storj.io/minio/cmd/logger"
    32  	"storj.io/minio/pkg/env"
    33  )
    34  
    35  // RetMode - object retention mode.
    36  type RetMode string
    37  
    38  const (
    39  	// RetGovernance - governance mode.
    40  	RetGovernance RetMode = "GOVERNANCE"
    41  
    42  	// RetCompliance - compliance mode.
    43  	RetCompliance RetMode = "COMPLIANCE"
    44  )
    45  
    46  // Valid - returns if retention mode is valid
    47  func (r RetMode) Valid() bool {
    48  	switch r {
    49  	case RetGovernance, RetCompliance:
    50  		return true
    51  	}
    52  	return false
    53  }
    54  
    55  func parseRetMode(modeStr string) (mode RetMode) {
    56  	switch strings.ToUpper(modeStr) {
    57  	case "GOVERNANCE":
    58  		mode = RetGovernance
    59  	case "COMPLIANCE":
    60  		mode = RetCompliance
    61  	}
    62  	return mode
    63  }
    64  
    65  // LegalHoldStatus - object legal hold status.
    66  type LegalHoldStatus string
    67  
    68  const (
    69  	// LegalHoldOn - legal hold is on.
    70  	LegalHoldOn LegalHoldStatus = "ON"
    71  
    72  	// LegalHoldOff - legal hold is off.
    73  	LegalHoldOff LegalHoldStatus = "OFF"
    74  )
    75  
    76  // Valid - returns true if legal hold status has valid values
    77  func (l LegalHoldStatus) Valid() bool {
    78  	switch l {
    79  	case LegalHoldOn, LegalHoldOff:
    80  		return true
    81  	}
    82  	return false
    83  }
    84  
    85  func parseLegalHoldStatus(holdStr string) (st LegalHoldStatus) {
    86  	switch strings.ToUpper(holdStr) {
    87  	case "ON":
    88  		st = LegalHoldOn
    89  	case "OFF":
    90  		st = LegalHoldOff
    91  	}
    92  	return st
    93  }
    94  
    95  // Bypass retention governance header.
    96  const (
    97  	AmzObjectLockBypassRetGovernance = "X-Amz-Bypass-Governance-Retention"
    98  	AmzObjectLockRetainUntilDate     = "X-Amz-Object-Lock-Retain-Until-Date"
    99  	AmzObjectLockMode                = "X-Amz-Object-Lock-Mode"
   100  	AmzObjectLockLegalHold           = "X-Amz-Object-Lock-Legal-Hold"
   101  )
   102  
   103  var (
   104  	// ErrMalformedBucketObjectConfig -indicates that the bucket object lock config is malformed
   105  	ErrMalformedBucketObjectConfig = errors.New("invalid bucket object lock config")
   106  	// ErrInvalidRetentionDate - indicates that retention date needs to be in ISO 8601 format
   107  	ErrInvalidRetentionDate = errors.New("date must be provided in ISO 8601 format")
   108  	// ErrPastObjectLockRetainDate - indicates that retention date must be in the future
   109  	ErrPastObjectLockRetainDate = errors.New("the retain until date must be in the future")
   110  	// ErrUnknownWORMModeDirective - indicates that the retention mode is invalid
   111  	ErrUnknownWORMModeDirective = errors.New("unknown WORM mode directive")
   112  	// ErrObjectLockMissingContentMD5 - indicates missing Content-MD5 header for put object requests with locking
   113  	ErrObjectLockMissingContentMD5 = errors.New("content-MD5 HTTP header is required for Put Object requests with Object Lock parameters")
   114  	// ErrObjectLockInvalidHeaders indicates that object lock headers are missing
   115  	ErrObjectLockInvalidHeaders = errors.New("x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied")
   116  	// ErrMalformedXML - generic error indicating malformed XML
   117  	ErrMalformedXML = errors.New("the XML you provided was not well-formed or did not validate against our published schema")
   118  )
   119  
   120  const (
   121  	ntpServerEnv = "MINIO_NTP_SERVER"
   122  )
   123  
   124  var (
   125  	ntpServer = env.Get(ntpServerEnv, "")
   126  )
   127  
   128  // UTCNowNTP - is similar in functionality to UTCNow()
   129  // but only used when we do not wish to rely on system
   130  // time.
   131  func UTCNowNTP() (time.Time, error) {
   132  	// ntp server is disabled
   133  	if ntpServer == "" {
   134  		return time.Now().UTC(), nil
   135  	}
   136  	return ntp.Time(ntpServer)
   137  }
   138  
   139  // Retention - bucket level retention configuration.
   140  type Retention struct {
   141  	Mode        RetMode
   142  	Validity    time.Duration
   143  	LockEnabled bool
   144  }
   145  
   146  // Retain - check whether given date is retainable by validity time.
   147  func (r Retention) Retain(created time.Time) bool {
   148  	t, err := UTCNowNTP()
   149  	if err != nil {
   150  		logger.LogIf(context.Background(), err)
   151  		// Retain
   152  		return true
   153  	}
   154  	return created.Add(r.Validity).After(t)
   155  }
   156  
   157  // DefaultRetention - default retention configuration.
   158  type DefaultRetention struct {
   159  	XMLName xml.Name `xml:"DefaultRetention"`
   160  	Mode    RetMode  `xml:"Mode"`
   161  	Days    *uint64  `xml:"Days"`
   162  	Years   *uint64  `xml:"Years"`
   163  }
   164  
   165  // Maximum support retention days and years supported by AWS S3.
   166  const (
   167  	// This tested by using `mc lock` command
   168  	maximumRetentionDays  = 36500
   169  	maximumRetentionYears = 100
   170  )
   171  
   172  // UnmarshalXML - decodes XML data.
   173  func (dr *DefaultRetention) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
   174  	// Make subtype to avoid recursive UnmarshalXML().
   175  	type defaultRetention DefaultRetention
   176  	retention := defaultRetention{}
   177  
   178  	if err := d.DecodeElement(&retention, &start); err != nil {
   179  		return err
   180  	}
   181  
   182  	switch retention.Mode {
   183  	case RetGovernance, RetCompliance:
   184  	default:
   185  		return fmt.Errorf("unknown retention mode %v", retention.Mode)
   186  	}
   187  
   188  	if retention.Days == nil && retention.Years == nil {
   189  		return fmt.Errorf("either Days or Years must be specified")
   190  	}
   191  
   192  	if retention.Days != nil && retention.Years != nil {
   193  		return fmt.Errorf("either Days or Years must be specified, not both")
   194  	}
   195  
   196  	if retention.Days != nil {
   197  		if *retention.Days == 0 {
   198  			return fmt.Errorf("Default retention period must be a positive integer value for 'Days'")
   199  		}
   200  		if *retention.Days > maximumRetentionDays {
   201  			return fmt.Errorf("Default retention period too large for 'Days' %d", *retention.Days)
   202  		}
   203  	} else if *retention.Years == 0 {
   204  		return fmt.Errorf("Default retention period must be a positive integer value for 'Years'")
   205  	} else if *retention.Years > maximumRetentionYears {
   206  		return fmt.Errorf("Default retention period too large for 'Years' %d", *retention.Years)
   207  	}
   208  
   209  	*dr = DefaultRetention(retention)
   210  
   211  	return nil
   212  }
   213  
   214  // Config - object lock configuration specified in
   215  // https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_ObjectLockConfiguration.html
   216  type Config struct {
   217  	XMLNS             string   `xml:"xmlns,attr,omitempty"`
   218  	XMLName           xml.Name `xml:"ObjectLockConfiguration"`
   219  	ObjectLockEnabled string   `xml:"ObjectLockEnabled"`
   220  	Rule              *struct {
   221  		DefaultRetention DefaultRetention `xml:"DefaultRetention"`
   222  	} `xml:"Rule,omitempty"`
   223  }
   224  
   225  // UnmarshalXML - decodes XML data.
   226  func (config *Config) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
   227  	// Make subtype to avoid recursive UnmarshalXML().
   228  	type objectLockConfig Config
   229  	parsedConfig := objectLockConfig{}
   230  
   231  	if err := d.DecodeElement(&parsedConfig, &start); err != nil {
   232  		return err
   233  	}
   234  
   235  	if parsedConfig.ObjectLockEnabled != "Enabled" {
   236  		return fmt.Errorf("only 'Enabled' value is allowed to ObjectLockEnabled element")
   237  	}
   238  
   239  	*config = Config(parsedConfig)
   240  	return nil
   241  }
   242  
   243  // ToRetention - convert to Retention type.
   244  func (config *Config) ToRetention() Retention {
   245  	r := Retention{
   246  		LockEnabled: config.ObjectLockEnabled == "Enabled",
   247  	}
   248  	if config.Rule != nil {
   249  		r.Mode = config.Rule.DefaultRetention.Mode
   250  
   251  		t, err := UTCNowNTP()
   252  		if err != nil {
   253  			logger.LogIf(context.Background(), err)
   254  			// Do not change any configuration
   255  			// upon NTP failure.
   256  			return r
   257  		}
   258  
   259  		if config.Rule.DefaultRetention.Days != nil {
   260  			r.Validity = t.AddDate(0, 0, int(*config.Rule.DefaultRetention.Days)).Sub(t)
   261  		} else {
   262  			r.Validity = t.AddDate(int(*config.Rule.DefaultRetention.Years), 0, 0).Sub(t)
   263  		}
   264  	}
   265  
   266  	return r
   267  }
   268  
   269  // Maximum 4KiB size per object lock config.
   270  const maxObjectLockConfigSize = 1 << 12
   271  
   272  // ParseObjectLockConfig parses ObjectLockConfig from xml
   273  func ParseObjectLockConfig(reader io.Reader) (*Config, error) {
   274  	config := Config{}
   275  	if err := xml.NewDecoder(io.LimitReader(reader, maxObjectLockConfigSize)).Decode(&config); err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	return &config, nil
   280  }
   281  
   282  // NewObjectLockConfig returns a initialized lock.Config struct
   283  func NewObjectLockConfig() *Config {
   284  	return &Config{
   285  		ObjectLockEnabled: "Enabled",
   286  	}
   287  }
   288  
   289  // RetentionDate is a embedded type containing time.Time to unmarshal
   290  // Date in Retention
   291  type RetentionDate struct {
   292  	time.Time
   293  }
   294  
   295  // UnmarshalXML parses date from Retention and validates date format
   296  func (rDate *RetentionDate) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error {
   297  	var dateStr string
   298  	err := d.DecodeElement(&dateStr, &startElement)
   299  	if err != nil {
   300  		return err
   301  	}
   302  	// While AWS documentation mentions that the date specified
   303  	// must be present in ISO 8601 format, in reality they allow
   304  	// users to provide RFC 3339 compliant dates.
   305  	retDate, err := time.Parse(time.RFC3339, dateStr)
   306  	if err != nil {
   307  		return ErrInvalidRetentionDate
   308  	}
   309  
   310  	*rDate = RetentionDate{retDate}
   311  	return nil
   312  }
   313  
   314  // MarshalXML encodes expiration date if it is non-zero and encodes
   315  // empty string otherwise
   316  func (rDate *RetentionDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
   317  	if *rDate == (RetentionDate{time.Time{}}) {
   318  		return nil
   319  	}
   320  	return e.EncodeElement(rDate.Format(time.RFC3339), startElement)
   321  }
   322  
   323  // ObjectRetention specified in
   324  // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectRetention.html
   325  type ObjectRetention struct {
   326  	XMLNS           string        `xml:"xmlns,attr,omitempty"`
   327  	XMLName         xml.Name      `xml:"Retention"`
   328  	Mode            RetMode       `xml:"Mode,omitempty"`
   329  	RetainUntilDate RetentionDate `xml:"RetainUntilDate,omitempty"`
   330  }
   331  
   332  // Maximum 4KiB size per object retention config.
   333  const maxObjectRetentionSize = 1 << 12
   334  
   335  // ParseObjectRetention constructs ObjectRetention struct from xml input
   336  func ParseObjectRetention(reader io.Reader) (*ObjectRetention, error) {
   337  	ret := ObjectRetention{}
   338  	if err := xml.NewDecoder(io.LimitReader(reader, maxObjectRetentionSize)).Decode(&ret); err != nil {
   339  		return nil, err
   340  	}
   341  	if ret.Mode != "" && !ret.Mode.Valid() {
   342  		return &ret, ErrUnknownWORMModeDirective
   343  	}
   344  
   345  	if ret.Mode.Valid() && ret.RetainUntilDate.IsZero() {
   346  		return &ret, ErrMalformedXML
   347  	}
   348  
   349  	if !ret.Mode.Valid() && !ret.RetainUntilDate.IsZero() {
   350  		return &ret, ErrMalformedXML
   351  	}
   352  
   353  	t, err := UTCNowNTP()
   354  	if err != nil {
   355  		logger.LogIf(context.Background(), err)
   356  		return &ret, ErrPastObjectLockRetainDate
   357  	}
   358  
   359  	if !ret.RetainUntilDate.IsZero() && ret.RetainUntilDate.Before(t) {
   360  		return &ret, ErrPastObjectLockRetainDate
   361  	}
   362  
   363  	return &ret, nil
   364  }
   365  
   366  // IsObjectLockRetentionRequested returns true if object lock retention headers are set.
   367  func IsObjectLockRetentionRequested(h http.Header) bool {
   368  	if _, ok := h[AmzObjectLockMode]; ok {
   369  		return true
   370  	}
   371  	if _, ok := h[AmzObjectLockRetainUntilDate]; ok {
   372  		return true
   373  	}
   374  	return false
   375  }
   376  
   377  // IsObjectLockLegalHoldRequested returns true if object lock legal hold header is set.
   378  func IsObjectLockLegalHoldRequested(h http.Header) bool {
   379  	_, ok := h[AmzObjectLockLegalHold]
   380  	return ok
   381  }
   382  
   383  // IsObjectLockGovernanceBypassSet returns true if object lock governance bypass header is set.
   384  func IsObjectLockGovernanceBypassSet(h http.Header) bool {
   385  	return strings.ToLower(h.Get(AmzObjectLockBypassRetGovernance)) == "true"
   386  }
   387  
   388  // IsObjectLockRequested returns true if legal hold or object lock retention headers are requested.
   389  func IsObjectLockRequested(h http.Header) bool {
   390  	return IsObjectLockLegalHoldRequested(h) || IsObjectLockRetentionRequested(h)
   391  }
   392  
   393  // ParseObjectLockRetentionHeaders parses http headers to extract retention mode and retention date
   394  func ParseObjectLockRetentionHeaders(h http.Header) (rmode RetMode, r RetentionDate, err error) {
   395  	retMode := h.Get(AmzObjectLockMode)
   396  	dateStr := h.Get(AmzObjectLockRetainUntilDate)
   397  	if len(retMode) == 0 || len(dateStr) == 0 {
   398  		return rmode, r, ErrObjectLockInvalidHeaders
   399  	}
   400  
   401  	rmode = parseRetMode(retMode)
   402  	if !rmode.Valid() {
   403  		return rmode, r, ErrUnknownWORMModeDirective
   404  	}
   405  
   406  	var retDate time.Time
   407  	// While AWS documentation mentions that the date specified
   408  	// must be present in ISO 8601 format, in reality they allow
   409  	// users to provide RFC 3339 compliant dates.
   410  	retDate, err = time.Parse(time.RFC3339, dateStr)
   411  	if err != nil {
   412  		return rmode, r, ErrInvalidRetentionDate
   413  	}
   414  
   415  	t, err := UTCNowNTP()
   416  	if err != nil {
   417  		logger.LogIf(context.Background(), err)
   418  		return rmode, r, ErrPastObjectLockRetainDate
   419  	}
   420  
   421  	if retDate.Before(t) {
   422  		return rmode, r, ErrPastObjectLockRetainDate
   423  	}
   424  
   425  	return rmode, RetentionDate{retDate}, nil
   426  
   427  }
   428  
   429  // GetObjectRetentionMeta constructs ObjectRetention from metadata
   430  func GetObjectRetentionMeta(meta map[string]string) ObjectRetention {
   431  	var mode RetMode
   432  	var retainTill RetentionDate
   433  
   434  	var modeStr, tillStr string
   435  	ok := false
   436  
   437  	modeStr, ok = meta[strings.ToLower(AmzObjectLockMode)]
   438  	if !ok {
   439  		modeStr, ok = meta[AmzObjectLockMode]
   440  	}
   441  	if ok {
   442  		mode = parseRetMode(modeStr)
   443  	} else {
   444  		return ObjectRetention{}
   445  	}
   446  
   447  	tillStr, ok = meta[strings.ToLower(AmzObjectLockRetainUntilDate)]
   448  	if !ok {
   449  		tillStr, ok = meta[AmzObjectLockRetainUntilDate]
   450  	}
   451  	if ok {
   452  		if t, e := time.Parse(time.RFC3339, tillStr); e == nil {
   453  			retainTill = RetentionDate{t.UTC()}
   454  		}
   455  	}
   456  	return ObjectRetention{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", Mode: mode, RetainUntilDate: retainTill}
   457  }
   458  
   459  // GetObjectLegalHoldMeta constructs ObjectLegalHold from metadata
   460  func GetObjectLegalHoldMeta(meta map[string]string) ObjectLegalHold {
   461  	holdStr, ok := meta[strings.ToLower(AmzObjectLockLegalHold)]
   462  	if !ok {
   463  		holdStr, ok = meta[AmzObjectLockLegalHold]
   464  	}
   465  	if ok {
   466  		return ObjectLegalHold{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", Status: parseLegalHoldStatus(holdStr)}
   467  	}
   468  	return ObjectLegalHold{}
   469  }
   470  
   471  // ParseObjectLockLegalHoldHeaders parses request headers to construct ObjectLegalHold
   472  func ParseObjectLockLegalHoldHeaders(h http.Header) (lhold ObjectLegalHold, err error) {
   473  	holdStatus, ok := h[AmzObjectLockLegalHold]
   474  	if ok {
   475  		lh := parseLegalHoldStatus(holdStatus[0])
   476  		if !lh.Valid() {
   477  			return lhold, ErrUnknownWORMModeDirective
   478  		}
   479  		lhold = ObjectLegalHold{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", Status: lh}
   480  	}
   481  	return lhold, nil
   482  
   483  }
   484  
   485  // ObjectLegalHold specified in
   486  // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html
   487  type ObjectLegalHold struct {
   488  	XMLNS   string          `xml:"xmlns,attr,omitempty"`
   489  	XMLName xml.Name        `xml:"LegalHold"`
   490  	Status  LegalHoldStatus `xml:"Status,omitempty"`
   491  }
   492  
   493  // UnmarshalXML - decodes XML data.
   494  func (l *ObjectLegalHold) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
   495  	switch start.Name.Local {
   496  	case "LegalHold", "ObjectLockLegalHold":
   497  	default:
   498  		return xml.UnmarshalError(fmt.Sprintf("expected element type <LegalHold>/<ObjectLockLegalHold> but have <%s>",
   499  			start.Name.Local))
   500  	}
   501  	for {
   502  		// Read tokens from the XML document in a stream.
   503  		t, err := d.Token()
   504  		if err != nil {
   505  			if err == io.EOF {
   506  				break
   507  			}
   508  			return err
   509  		}
   510  
   511  		switch se := t.(type) {
   512  		case xml.StartElement:
   513  			switch se.Name.Local {
   514  			case "Status":
   515  				var st LegalHoldStatus
   516  				if err = d.DecodeElement(&st, &se); err != nil {
   517  					return err
   518  				}
   519  				l.Status = st
   520  			default:
   521  				return xml.UnmarshalError(fmt.Sprintf("expected element type <Status> but have <%s>", se.Name.Local))
   522  			}
   523  		}
   524  	}
   525  	return nil
   526  }
   527  
   528  // IsEmpty returns true if struct is empty
   529  func (l *ObjectLegalHold) IsEmpty() bool {
   530  	return !l.Status.Valid()
   531  }
   532  
   533  // ParseObjectLegalHold decodes the XML into ObjectLegalHold
   534  func ParseObjectLegalHold(reader io.Reader) (hold *ObjectLegalHold, err error) {
   535  	hold = &ObjectLegalHold{}
   536  	if err = xml.NewDecoder(reader).Decode(hold); err != nil {
   537  		return
   538  	}
   539  
   540  	if !hold.Status.Valid() {
   541  		return nil, ErrMalformedXML
   542  	}
   543  	return
   544  }
   545  
   546  // FilterObjectLockMetadata filters object lock metadata if s3:GetObjectRetention permission is denied or if isCopy flag set.
   547  func FilterObjectLockMetadata(metadata map[string]string, filterRetention, filterLegalHold bool) map[string]string {
   548  	// Copy on write
   549  	dst := metadata
   550  	var copied bool
   551  	delKey := func(key string) {
   552  		if _, ok := metadata[key]; !ok {
   553  			return
   554  		}
   555  		if !copied {
   556  			dst = make(map[string]string, len(metadata))
   557  			for k, v := range metadata {
   558  				dst[k] = v
   559  			}
   560  			copied = true
   561  		}
   562  		delete(dst, key)
   563  	}
   564  	legalHold := GetObjectLegalHoldMeta(metadata)
   565  	if !legalHold.Status.Valid() || filterLegalHold {
   566  		delKey(AmzObjectLockLegalHold)
   567  	}
   568  
   569  	ret := GetObjectRetentionMeta(metadata)
   570  	if !ret.Mode.Valid() || filterRetention {
   571  		delKey(AmzObjectLockMode)
   572  		delKey(AmzObjectLockRetainUntilDate)
   573  		return dst
   574  	}
   575  	return dst
   576  }