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 }