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 }