From fd0fa4e5c50dad1d5b156dc37bd96024b3e12b5d Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 21 Nov 2019 04:52:35 -0800 Subject: [PATCH] Add NTP retention time (#8548) --- cmd/object-lock.go | 74 +++++++++++++++++++++++++++++++++++++++------- cmd/utils.go | 12 ++++++++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 79 insertions(+), 10 deletions(-) diff --git a/cmd/object-lock.go b/cmd/object-lock.go index 7d689b375..80a68dcb8 100644 --- a/cmd/object-lock.go +++ b/cmd/object-lock.go @@ -28,6 +28,8 @@ import ( "time" xhttp "github.com/minio/minio/cmd/http" + "github.com/minio/minio/cmd/logger" + "github.com/minio/minio/pkg/env" ) // RetentionMode - object retention mode. @@ -65,6 +67,14 @@ var ( errObjectLockInvalidHeaders = errors.New("x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied") ) +const ( + ntpServerEnv = "MINIO_NTP_SERVER" +) + +var ( + ntpServer = env.Get(ntpServerEnv, "") +) + // Retention - bucket level retention configuration. type Retention struct { Mode RetentionMode @@ -78,7 +88,13 @@ func (r Retention) IsEmpty() bool { // Retain - check whether given date is retainable by validity time. func (r Retention) Retain(created time.Time) bool { - return globalWORMEnabled || created.Add(r.Validity).After(time.Now()) + t, err := UTCNowNTP() + if err != nil { + logger.LogIf(context.Background(), err) + // Retain + return true + } + return globalWORMEnabled || created.Add(r.Validity).After(t) } // BucketObjectLockConfig - map of bucket and retention configuration. @@ -205,11 +221,19 @@ func (config *ObjectLockConfig) UnmarshalXML(d *xml.Decoder, start xml.StartElem func (config *ObjectLockConfig) ToRetention() (r Retention) { if config.Rule != nil { r.Mode = config.Rule.DefaultRetention.Mode - utcNow := time.Now().UTC() + + t, err := UTCNowNTP() + if err != nil { + logger.LogIf(context.Background(), err) + // Do not change any configuration + // upon NTP failure. + return r + } + if config.Rule.DefaultRetention.Days != nil { - r.Validity = utcNow.AddDate(0, 0, int(*config.Rule.DefaultRetention.Days)).Sub(utcNow) + r.Validity = t.AddDate(0, 0, int(*config.Rule.DefaultRetention.Days)).Sub(t) } else { - r.Validity = utcNow.AddDate(int(*config.Rule.DefaultRetention.Years), 0, 0).Sub(utcNow) + r.Validity = t.AddDate(int(*config.Rule.DefaultRetention.Years), 0, 0).Sub(t) } } @@ -282,9 +306,17 @@ func parseObjectRetention(reader io.Reader) (*ObjectRetention, error) { if ret.Mode != Compliance && ret.Mode != Governance { return &ret, errUnknownWORMModeDirective } - if ret.RetainUntilDate.Before(time.Now()) { + + t, err := UTCNowNTP() + if err != nil { + logger.LogIf(context.Background(), err) + return &ret, errPastObjectLockRetainDate + } + + if ret.RetainUntilDate.Before(t) { return &ret, errPastObjectLockRetainDate } + return &ret, nil } @@ -334,7 +366,14 @@ func parseObjectLockRetentionHeaders(h http.Header) (rmode RetentionMode, r Rete if err != nil { return rmode, r, errInvalidRetentionDate } - if retDate.Before(time.Now()) { + + t, err := UTCNowNTP() + if err != nil { + logger.LogIf(context.Background(), err) + return rmode, r, errPastObjectLockRetainDate + } + + if retDate.Before(t) { return rmode, r, errPastObjectLockRetainDate } } @@ -392,7 +431,12 @@ func checkGovernanceBypassAllowed(ctx context.Context, r *http.Request, bucket, } if ret.Mode == Governance { if !isObjectLockGovernanceBypassSet(r.Header) { - if ret.RetainUntilDate.After(UTCNow()) { + t, err := UTCNowNTP() + if err != nil { + logger.LogIf(ctx, err) + return oi, ErrObjectLocked + } + if ret.RetainUntilDate.After(t) { return oi, ErrObjectLocked } return oi, ErrNone @@ -440,7 +484,12 @@ func checkPutObjectRetentionAllowed(ctx context.Context, r *http.Request, bucket return mode, retainDate, toAPIErrorCode(ctx, err) } // AWS S3 just creates a new version of object when an object is being overwritten. - if objExists && retainDate.After(UTCNow()) { + t, err := UTCNowNTP() + if err != nil { + logger.LogIf(ctx, err) + return mode, retainDate, ErrObjectLocked + } + if objExists && retainDate.After(t) { return mode, retainDate, ErrObjectLocked } if rMode == Invalid { @@ -455,12 +504,17 @@ func checkPutObjectRetentionAllowed(ctx context.Context, r *http.Request, bucket if retentionPermErr != ErrNone { return mode, retainDate, retentionPermErr } + t, err := UTCNowNTP() + if err != nil { + logger.LogIf(ctx, err) + return mode, retainDate, ErrObjectLocked + } // AWS S3 just creates a new version of object when an object is being overwritten. - if objExists && retainDate.After(UTCNow()) { + if objExists && retainDate.After(t) { return mode, retainDate, ErrObjectLocked } // inherit retention from bucket configuration - return retention.Mode, RetentionDate{UTCNow().Add(retention.Validity)}, ErrNone + return retention.Mode, RetentionDate{t.Add(retention.Validity)}, ErrNone } return mode, retainDate, ErrNone } diff --git a/cmd/utils.go b/cmd/utils.go index c854e0995..358ed6a7a 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -35,6 +35,7 @@ import ( "strings" "time" + "github.com/beevik/ntp" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/handlers" @@ -314,6 +315,17 @@ func UTCNow() time.Time { return time.Now().UTC() } +// UTCNowNTP - is similar in functionality to UTCNow() +// but only used when we do not wish to rely on system +// time. +func UTCNowNTP() (time.Time, error) { + // ntp server is disabled + if ntpServer == "" { + return UTCNow(), nil + } + return ntp.Time(ntpServer) +} + // GenETag - generate UUID based ETag func GenETag() string { return ToS3ETag(getMD5Hash([]byte(mustGetUUID()))) diff --git a/go.mod b/go.mod index 139a66fdb..3ae6456ba 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 github.com/aws/aws-sdk-go v1.20.21 github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 + github.com/beevik/ntp v0.2.0 github.com/cheggaaa/pb v1.0.28 github.com/coredns/coredns v1.4.0 github.com/coreos/etcd v3.3.12+incompatible diff --git a/go.sum b/go.sum index 8f61bb103..a5bfae6d2 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7 github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM= github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M= +github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= +github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=