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