首页
/ MinIO Object Lock 深度指南:桶级 WORM 配置、对象保留与法律保留的实现原理

MinIO Object Lock 深度指南:桶级 WORM 配置、对象保留与法律保留的实现原理

2026-09-06 10:16:26作者:姚月梅Lane

本篇技术文章以 MinIO 仓库中的 Object Lock 使用文档 为主线,系统讲解如何通过 S3 兼容 API 为桶和对象配置不可变保护(WORM),并结合 桶锁服务锁核心库 等源码,剖析 GOVERNANCE / COMPLIANCE 两种保留模式的强制删除拦截逻辑、治理权限绕过机制,以及 MINIO_NTP_SERVER 时间源的实现细节,帮助你从命令实操到源码原理完整掌握 MinIO 的对象不可变能力。

一、核心概念:WORM、保留模式与法律保留

MinIO 服务器允许对特定对象启用 WORM(Write Once Read Many),也可以为桶配置默认对象锁设置——该设置会把默认的保留模式(retention mode)和保留期限(retention duration)应用到桶内所有对象,使对象变为不可变(immutable):在桶的对象锁配置或对象自身保留期到期之前,不允许删除该对象的任何版本。

从源码可以确认 MinIO 支持的两种保留模式常量定义在 RetMode

const (
    // RetGovernance - governance mode.
    RetGovernance RetMode = "GOVERNANCE"

    // RetCompliance - compliance mode.
    RetCompliance RetMode = "COMPLIANCE"
)
  • GOVERNANCE(治理模式):大多数用户无法覆盖或删除受保护的对象版本,但拥有特殊权限的用户(需 s3:BypassGovernanceRetention)可以修改保留设置或删除对象。适合在正式启用合规模式之前先试验保留期设置。
  • COMPLIANCE(合规模式):在保留期内,任何人都不能删除对象——包括根用户;保留模式不能变更、保留期不能缩短。
  • 法律保留(Legal Hold):独立于保留期存在。处于法律保留状态的对象,在保留被显式移除前,DeleteObjectVersion() 会失败。其状态值定义为 LegalHoldStatusON / OFF)。

二、前置条件:桶创建时必须启用对象锁

对象锁要求在桶创建时就启用。使用 mc 客户端时需要加 --with-lock 参数:

mc mb --with-lock myminio/testbucket

两个关键约束:

  1. 对象锁会自动在桶上启用版本控制(versioning),且一旦开启无法关闭;
  2. 已存在但未启用对象锁的桶,后补对象锁配置会被服务端直接拒绝。这一点在 PutBucketObjectLockConfigHandler 中有明确的源码证据:
// Deny object locking configuration settings on existing buckets without object lock enabled.
if _, _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil {
    if _, ok := err.(BucketObjectLockConfigNotFound); ok {
        writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrObjectLockConfigurationNotAllowed), r.URL)
    }
    ...
}

也就是说,对没有锁配置的桶执行 put-object-lock-configuration 会返回 ObjectLockConfigurationNotAllowed 错误,而不是静默生效。

三、配置桶级 WORM 策略

桶级 WORM 通过设置对象锁配置来开启,该配置会应用于桶中的对象。示例:为 mybucket 设置 GOVERNANCE 模式和 1 天保留期:

aws s3api put-object-lock-configuration --bucket mybucket \
  --object-lock-configuration 'ObjectLockEnabled="Enabled",Rule={DefaultRetention={Mode="GOVERNANCE",Days=1}}'

请求体对应的 XML 结构由 Config 定义,对应 S3 规范中的 ObjectLockConfiguration

<ObjectLockConfiguration>
    <ObjectLockEnabled>Enabled</ObjectLockEnabled>
    <Rule>
        <DefaultRetention>
            <Mode>GOVERNANCE</Mode>
            <Days>1</Days>
        </DefaultRetention>
    </Rule>
</ObjectLockConfiguration>

参数校验规则(源码级)

ObjectLockEnabled 只允许 Enabled 值(见 Config.UnmarshalXML)。DefaultRetentionXML 解析校验 强制以下规则:

规则 说明
DaysYears 必须二选一 两者都不给或同时给都会报错
天数上限 最大 36500 天(约 100 年),超限报 Default retention period too large for 'Days'
年数上限 最大 100 年,超限报 Default retention period too large for 'Years'
必须为正整数 DaysYears 为 0 会报 Default retention period must be a positive integer value
配置体大小 对象锁配置体最大 4KiB(maxObjectLockConfigSize = 1 << 12,见 ParseObjectLockConfig

新对象如何继承桶级保留

上传对象时,checkPutObjectLockAllowed 负责决定是否套用桶配置。其继承逻辑如下(节选自 cmd/bucket-object-lock.go):

if !retentionRequested && retentionCfg.Validity > 0 {
    // ...
    if !legalHoldRequested && retentionCfg.LockEnabled {
        // inherit retention from bucket configuration
        return retentionCfg.Mode, objectlock.RetentionDate{Time: t.Add(retentionCfg.Validity)}, legalHold, ErrNone
    }
}

即:当对象上传请求没有携带保留头、且桶锁配置有效时,对象自动继承桶的保留模式,保留截止时间为「当前时间 + 桶配置的有效期」。桶配置中的 Days/Years 会先被换算成 Validity 时长(见 ToRetention)。这也印证了文档 Concepts 部分的三条要点:

  • 对象锁配置设置后,新上传的对象自动继承桶的保留设置;
  • 上传时可以选择性地显式设置保留头(优先级高于桶配置);
  • 对象上传后,仍可通过 PutObjectRetention API 修改其保留设置(受权限与不可缩短规则约束)。

四、对象级保留:通过 HTTP 头设置

PutObject API 允许使用 x-amz-object-lock-modex-amz-object-lock-retain-until-date 头为单个对象设置保留模式与保留截止日期,其优先级高于桶的对象锁配置。命令行示例:

aws s3api put-object --bucket testbucket --key lockme \
  --object-lock-mode GOVERNANCE \
  --object-lock-retain-until-date "2035-11-20T00:00:00Z" \
  --body /etc/issue

源码中头名常量统一定义在 lock.go

const (
    AmzObjectLockBypassRetGovernance = "X-Amz-Bypass-Governance-Retention"
    AmzObjectLockRetainUntilDate     = "X-Amz-Object-Lock-Retain-Until-Date"
    AmzObjectLockMode                = "X-Amz-Object-Lock-Mode"
    AmzObjectLockLegalHold           = "X-Amz-Object-Lock-Legal-Hold"
)

解析时需要注意两点(见 ParseObjectLockRetentionHeaders):

  • 两个头必须同时提供,否则返回 ErrObjectLockInvalidHeaders("x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied");
  • 保留截止日期必须晚于当前时间(ISO 8601 格式,服务端实际也接受 RFC 3339 合规日期),否则返回 ErrPastObjectLockRetainDate("the retain until date must be in the future")。

上传后的对象还可以单独调整保留设置:

aws s3api put-object-retention --bucket testbucket --key lockme \
  --retention 'Mode="GOVERNANCE",RetainUntilDate="2036-11-20T00:00:00Z"'

该请求由 PutObjectRetentionHandler 处理。调整规则由 enforceRetentionBypassForPut 强制:

  • 已过期:保留期过后可自由修改;
  • GOVERNANCE:未设置 bypass 头时,既不能改模式,也不能把保留截止日期缩短到早于现有截止日期;
  • COMPLIANCE:模式不可变更、截止日期只能延长,任何缩短尝试都会返回 ObjectLocked

五、法律保留(Legal Hold)

PutObject API 支持通过 x-amz-object-lock-legal-hold 头在上传时直接设置法律保留:

aws s3api put-object --bucket testbucket --key legalhold \
  --object-lock-legal-hold-status ON \
  --body /etc/issue

对象上传后也可单独设置/查询法律保留,对应处理器为 PutObjectLegalHoldHandlerGetObjectLegalHoldHandler,权限分别要求 s3:PutObjectLegalHolds3:GetObjectLegalHold

法律保留的强制删除保护体现在两个层面:

  1. 删除路径enforceRetentionForDeletion 首先检查法律保留——只要 LegalHoldOn 状态有效,对象即被判定为锁定;
  2. 覆盖路径checkPutObjectLockAllowed 中明确注释 Disallow overwriting an object on legal hold,处于法律保留的对象版本禁止被覆盖写入。

只有显式地把对应版本 ID 的法律保留改为 OFF,对象才恢复可删除状态。

六、删除与覆盖的强制逻辑(源码级)

删除对象时,DeleteObjectHandler 通过 SetEvalRetentionBypassFn 挂接了 enforceRetentionBypassForDelete,判定顺序为:

lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn {
    return ObjectLocked{}
}
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
if ret.Mode.Valid() {
    switch ret.Mode {
    case objectlock.RetCompliance:
        // 合规模式:保留期内任何人(含根用户)都不能删除
        if !ret.RetainUntilDate.Before(t) {
            return ObjectLocked{}
        }
        return nil
    case objectlock.RetGovernance:
        byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header)
        if !byPassSet {
            // 未带 bypass 头:保留期内拒绝
            if !ret.RetainUntilDate.Before(t) {
                return ObjectLocked{}
            }
            return nil
        }
        // 带了 bypass 头:还必须有 s3:BypassGovernanceRetention 权限
        if checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName) != ErrNone {
            return errAuthentication
        }
    }
}

关键行为归纳:

场景 结果
对象处于法律保留 一律返回 ObjectLocked,除非先解除保留
COMPLIANCE 且保留期内 任何用户均无法删除/覆盖
COMPLIANCE 且保留期已过 可删除/覆盖
GOVERNANCE 且保留期内、未带 bypass 头 拒绝
GOVERNANCE 且保留期内、带 x-amz-bypass-governance-retention: true 且具备 s3:BypassGovernanceRetention 权限 允许
GOVERNANCE 且保留期已过 可删除/覆盖

另外值得注意一个实操细节:对已启用对象锁的桶,禁止前缀强制删除(force delete)。DeleteObjectHandler 中:

rcfg, _ := globalBucketObjectLockSys.Get(bucket)
if rcfg.LockEnabled && opts.DeletePrefix {
    apiErr := toAPIError(ctx, errInvalidArgument)
    apiErr.Description = "force-delete is forbidden on Object Locking enabled buckets"
    writeErrorResponse(ctx, w, apiErr, r.URL)
}

aws s3 rm --recursive 这类批量删除在 WORM 桶上会被明确拒绝,删除只能按单个对象(及版本 ID)逐一进行。

还有一个健壮性细节:上述所有时间比较都通过 UTCNowNTP 获取时间,一旦时间源获取失败,系统采取保守策略——判定对象为锁定(见 enforceRetentionForDeletionreturn true),宁可不放行也不误删。

七、MINIO_NTP_SERVER:可控的保留期时间源

对象锁的判定高度依赖"当前时间"。文档指出:如果不想使用系统时间来计算保留日期,可以设置环境变量 MINIO_NTP_SERVER 指向远程 NTP 服务器。其实现非常直接(lock.go):

const (
    ntpServerEnv = "MINIO_NTP_SERVER"
)

var ntpServer = env.Get(ntpServerEnv, "")

// 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 time.Now().UTC(), nil
    }
    return ntp.Time(ntpServer)
}
  • 未设置时回退到本机 time.Now().UTC()
  • 设置后,所有保留期计算(上传继承、删除判定、保留头解析)都会向该 NTP 端点查询时间。

对多节点分布式部署尤其有意义:各节点系统时钟可能漂移,统一 NTP 源可避免"某节点认为已到期、另一节点认为未到期"的不一致。同时从 ToRetention 可以看到,NTP 查询失败时服务端会记录告警日志且不变更现有配置,而不是默认放行。

八、权限模型与策略条件

对象锁相关操作分别要求独立的 S3 权限动作,源码中可见以下校验点:

API 权限动作 校验位置
PutBucketObjectLockConfiguration s3:PutBucketObjectLockConfiguration PutBucketObjectLockConfigHandler
GetBucketObjectLockConfiguration s3:GetBucketObjectLockConfiguration GetBucketObjectLockConfigHandler
PutObjectRetention s3:PutObjectRetention isPutRetentionAllowed
PutObjectLegalHold s3:PutObjectLegalHold PutObjectLegalHoldHandler
GetObjectRetention / GetObjectLegalHold s3:GetObjectRetention / s3:GetObjectLegalHold GetObject 元数据过滤
治理模式删除绕过 s3:BypassGovernanceRetention enforceRetentionBypassForDelete

MinIO 在保留权限判定上比 S3 规范更精细:isPutRetentionAllowed 除了校验 s3:PutObjectRetention,还会把以下值注入策略条件(condition),供 IAM/桶策略使用:

conditions["object-lock-mode"] = []string{string(retMode)}
conditions["object-lock-retain-until-date"] = []string{retDate.UTC().Format(time.RFC3339)}
if retDays > 0 {
    conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
}

这意味着策略中可以用 object-lock-modeobject-lock-retain-until-dateobject-lock-remaining-retention-days 三个条件键来精细控制"谁能给对象设置何种模式、多长时间的保留"。此外,若 GetObject 请求者没有 s3:GetObjectRetention 权限,响应中的保留元数据会被 FilterObjectLockMetadata 主动过滤掉,避免敏感锁定信息泄露。

九、实操注意事项小结

综合文档与源码,落地 WORM 时需要注意的边界行为:

  1. 不可逆性mc mb --with-lock 创建后版本控制随之开启且对象锁无法事后关闭;对无锁的既有桶补配置会得到 ObjectLockConfigurationNotAllowed 错误;
  2. 模式差异:COMPLIANCE 一旦设置,保留期内模式与期限均不可变更/缩短;GOVERNANCE 允许被具备 bypass 权限的用户覆盖或删除,适合先测试再迁移到合规模式;
  3. 日期格式:保留截止日期需为 ISO 8601(实际兼容 RFC 3339)格式,且必须在未来,否则报 the retain until date must be in the future
  4. 桶级继承优先级:对象上传时显式携带的保留头 > 桶默认保留配置;桶配置的 Days/Years 上限分别为 36500 天与 100 年;
  5. 批量删除受限:WORM 桶禁止 force-delete 前缀删除,需按版本精确删除;
  6. 时间源:多节点环境建议设置 MINIO_NTP_SERVER,时间获取失败时系统保守地维持锁定状态;
  7. 法律保留独立生效:即使保留期已过,法律保留 ON 的对象依然不可删除,必须显式解除。

以上能力均通过标准 S3 兼容 API 暴露,mcawscli 等通用客户端均可直接使用,策略条件与权限动作的划分则与 MinIO 多用户 IAM 体系(cmd/auth-handler.go)无缝衔接,便于在企业环境中按合规要求做最小授权。

登录后查看全文
热门项目推荐
相关项目推荐