多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OPA/Rego 时间控制实战:基于 `time.clock` 与 `time.parse_ns` 实现“本地营业时间“ ABAC 授权

OPA/Rego 时间控制实战:基于 `time.clock` 与 `time.parse_ns` 实现“本地营业时间“ ABAC 授权 后端认证鉴权云原生【免费下载链接】opaOpen Policy Agent (OPA) is an open source, general-purpose policy engine.项目地址https://gitcode.com/gh_mirrors/op/opa点击查看免费下载导读在许多访问控制场景中权限并非只取决于用户身份或资源属性还取决于当前时间。Open Policy AgentOPA的 Rego 语言内置了完整的time内置函数族本篇文章以官方示例local_business_hours按用户本地营业时间授权为核心完整讲解如何用time.parse_ns解析请求时间、用time.clock提取用户所在时区的小时数并结合数据文档data.business_hours实现工作时段内放行、时段外拒绝的 ABAC 策略。读完本文你将掌握时间类策略的完整写法、时区处理原理以及这些内置函数在 topdown/time.go 中的底层实现细节。示例背景基于时间属性的 ABAC 需求示例文档 intro.md 指出基于属性的访问控制ABAC中一个常见需求是根据时间授予访问权限典型做法是先确定用户所处时区的本地时间再判断该本地时间是否落在允许的时段内。本示例展示的正是这一场景当请求由处于其本地营业时间内的用户发出时允许该请求。其完整工程位于仓库目录docs/docs/policy-reference/_examples/time/clock/local_business_hours/ ├── config.json # Playground 渲染配置showInput/showData/titleSize ├── data.json # 业务数据各时区的营业时段 ├── input.json # 模拟请求输入用户、时区、请求时间 ├── intro.md # 示例说明本文主体来源 ├── output.json # 期望输出用于验证策略正确性 ├── policy.rego # Rego 策略本体 └── title.txt # 示例标题这个示例同时被 docs/docs/policy-reference/builtins/time.mdx 中time.clock一节通过PlaygroundExample dir{require.context(../_examples/time/clock/local_business_hours)} /直接引用属于官方文档内置的交互式示例。策略代码逐行拆解核心策略 policy.rego 全文如下package play request_time : time.parse_ns(RFC822Z, input.request_time) local_hours : data.business_hours[input.tz] default allow : false allow if { [hour, _, _] : time.clock([request_time, input.tz]) hour local_hours.start hour local_hours.end }逐行解读request_time : time.parse_ns(RFC822Z, input.request_time)将输入中的字符串时间input.request_time按RFC822Z格式解析为自 Unix 纪元以来的纳秒数。RFC822Z是 Gotime包的内置格式常量布局为02 Jan 06 15:04 -0700支持带数字时区偏移的 RFC822 风格时间。local_hours : data.business_hours[input.tz]从数据文档中按用户时区如Asia/Tokyo取出营业时段对象含start、end两个字段代表起始/结束小时。default allow : false声明默认拒绝fail-closed。这是安全策略的通用最佳实践任何未明确匹配的情况一律拒绝。allow if { ... }条件规则体OPA 1.x 的if语法[hour, _, _] : time.clock([request_time, input.tz])把纳秒时间戳与用户时区组成二元数组传给time.clock解构出该时区下的本地hour丢弃 minute、secondhour local_hours.start且hour local_hours.end本地小时严格落在营业时段开区间内才允许。注意示例使用严格不等式与即 9:00:00 整点时刻不算已开始18:00:00 整点也不算未结束。若希望闭区间可改为hour local_hours.start与hour local_hours.end。输入与数据文档模拟请求 input.json{ user: y.hanakoexample.com, tz: Asia/Tokyo, request_time: 03 Jul 24 14:04 0000 }业务数据 data.json{ business_hours: { Asia/Tokyo: { start: 9, end: 18 } } }期望输出与验证output.json 给出该输入的期望求值结果{ allow: false, local_hours: { end: 18, start: 9 }, request_time: 1720015440000000000 }可以验证其正确性1720015440000000000纳秒 1720015440秒即2024-07-03 14:04:00 UTCRFC822Z格式中的0000表明该时间本身就是 UTC。东京时区Asia/Tokyo为 UTC9换算本地时间为23:04不在9 hour 18之内因此allow为false——用户处于本地营业时间之外请求被拒绝。这正是按用户本地时间判断营业时段的核心语义。底层原理一time.parse_ns是如何解析时间的time.parse_ns的声明位于 v1/ast/builtins.go签名为time.parse_ns(layout, value) → nslayout解析格式可传 Gotime包支持的内置常量名如RFC822Z也可以是自定义布局字符串value待解析的时间字符串返回纳秒级时间戳int64若结果超出int64可表示的时间范围则返回undefined。其实现builtinTimeParseNanos位于 v1/topdown/time.goformatStr : string(format) // look for the formatStr in our acceptedTimeFormats and // use the constant instead if it matches if f, ok : acceptedTimeFormats[formatStr]; ok { formatStr f } result, err : time.Parse(formatStr, string(value)) if err ! nil { return err } return toSafeUnixNano(result, iter)关键点有两个格式常量映射acceptedTimeFormatsv1/topdown/time.go将ANSIC、UnixDate、RubyDate、RFC822、RFC822Z、RFC850、RFC1123、RFC1123Z、RFC3339、RFC3339Nano等常量名映射为 Gotime包的对应布局其余字符串则直接当作 Go 参考时间布局2006-01-02T15:04:05Z07:00的变体传给time.Parse。安全范围检查toSafeUnixNanov1/topdown/time.go会拒绝超出int64纳秒表示范围的时间戳约 1677-09-21 至 2262-04-11避免溢出。此外官方文档 docs/docs/policy-reference/builtins/time.mdx 提醒同一查询内多次调用time.now_ns会返回相同的值保证策略求值的确定性。底层原理二time.clock如何拿到用户本地小时time.clock的声明位于 v1/ast/builtins.go签名支持两种入参形式单个数字纳秒时间戳按 UTC 解释二元数组[ns, timezone]纳秒时间戳 时区字符串示例采用此形式。返回[hour, minute, second]三元数组其中hour为 0-23。实现builtinClock位于 v1/topdown/time.gofunc builtinClock(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { t, _, err : tzTime(operands[0].Value) if err ! nil { return err } hour, minute, second : t.Clock() result : ast.NewArray(ast.InternedTerm(hour), ast.InternedTerm(minute), ast.InternedTerm(second)) return iter(ast.NewTerm(result)) }它先通过tzTimev1/topdown/time.go把入参规整为一个带时区的time.Time再调用 Go 标准库的t.Clock()取得本地hour/min/sec。tzTime的处理逻辑值得注意入参为数组时第一元素解析为纳秒数第二元素为时区名时区为空字符串或UTC时按 UTC 处理Local使用宿主时区其余按IANA 时区名如Asia/Tokyo、America/New_York通过time.LoadLocation加载加载结果缓存于进程级tzCache字典中配合tzCacheMutex保证并发安全避免每次求值重复加载时区数据代码顶部通过_ time/tzdata导入了 Go 的嵌入式时区数据因此即使运行环境文件系统没有 tzdata也能正常解析 IANA 时区名见 v1/topdown/time.go。这套机制决定了示例中time.clock([request_time, Asia/Tokyo])返回的hour是东京本地时间的小时数从而让营业时间判断天然带上用户视角而不是服务器所在时区。时区写法与格式参考根据 docs/docs/policy-reference/builtins/time.mdx时区字符串支持三种写法写法含义IANA 时区名如America/New_York、Asia/Tokyo按该时区解释时间UTC或等价于不传时区按 UTCLocal使用 OPA 运行环境的本地时区time.parse_ns支持的格式常量acceptedTimeFormats包括ANSIC、UnixDate、RubyDate、RFC822、RFC822Z、RFC850、RFC1123、RFC1123Z、RFC3339、RFC3339Nano。自定义布局时需遵循 Go 参考时间2006-01-02T15:04:05Z07:00的占位符约定例如年2006、月Jan/01、日02、时15、分04、秒05、AM/PM 标记PM。运行与验证方式该示例是官方 Playground 的交互示例config.json中showInput: true、showData: true表明 Playground 会同时展示输入与数据面板。在本地 CLI 中可用opa eval直接复现# 将 input.json 与 data.json 分别以 data 与 input 传入查询 allow 及中间量 opa eval --format pretty \ --data docs/docs/policy-reference/_examples/time/clock/local_business_hours/data.json \ --input docs/docs/policy-reference/_examples/time/clock/local_business_hours/input.json \ --data docs/docs/policy-reference/_examples/time/clock/local_business_hours/policy.rego \ data.play.allow若策略文件使用package play则查询路径为data.play.allow。也可以只做局部验证opa eval time.parse_ns(RFC822Z, 03 Jul 24 14:04 0000) opa eval time.clock([1720015440000000000, Asia/Tokyo])期望分别得到1720015440000000000与[23, 4, 0]与示例 output.json 的语义一致。同类示例串联时间策略的常见套路time内置函数在 docs/docs/policy-reference/_examples/time 下共有四组官方示例可组合出完整的时间策略工具箱clock/local_business_hours本文主体time.parse_nstime.clock实现按用户本地时段授权。format/local_timetime.format([request_time, input.tz, 15:04:05])把纳秒时间戳格式化为指定时区的人类可读字符串可用于在错误消息中展示本地时间示例中错误码E123拼接了本地时间策略见 policy.rego。now_ns/past用time.now_ns取当前时间纳秒与time.parse_rfc3339_ns解析的输入时间做/比较判断时间戳是否在过去——说明 Rego 中比较时间就像比较普通数字一样直观见 intro.md。parse_ns/period用time.parse_ns(2006-01-02, 1999-01-01)构造区间端点配合time.parse_rfc3339_ns(input.time)判断输入是否落在某个日期区间内见 policy.rego。此外time内置函数族还包含time.date返回[year, month, day]、time.weekday返回星期名、time.format、time.parse_duration_ns、time.add_date、time.diff等声明均可查阅 v1/ast/builtins.go 中time.*部分实现统一集中在 v1/topdown/time.go 的init()注册处v1/topdown/time.go。常见陷阱与最佳实践结合示例代码与源码实现总结时间类策略的实践要点统一以纳秒为单位Rego 的时间内置函数内部统一使用自 Unix 纪元起的纳秒数字符串输入必须先用time.parse_ns/time.parse_rfc3339_ns转换比较直接用、等数字运算符即可。显式指定时区判断用户本地营业时间必须把[ns, tz]二元数组传给time.clock否则默认按 UTC 解释会得出与用户实际感知不一致的结果。默认拒绝策略始终以default allow : false兜底未匹配的时区、未知格式或解析失败都会导致拒绝fail-closed符合安全策略的最佳实践。注意开闭区间语义示例使用严格不等式整点边界时刻如 9:00、18:00默认不满足条件需根据业务语义决定是否改用/。格式常量 vs 自定义布局能匹配RFC822Z、RFC3339等常量时优先用常量名自定义布局必须按 Go 参考时间2006-01-02T15:04:05Z07:00书写否则解析会报错或产生错误结果。时区数据可用性OPA 内嵌了time/tzdata即使运行环境缺少系统时区文件也能加载 IANA 时区若传入非法时区名time.LoadLocation会返回错误策略求值相应失败。小结按用户本地营业时间授权是 ABAC 中极具代表性的时间敏感场景。通过官方示例local_business_hours及其源码佐证可以看到time.parse_ns负责把任意格式的时间字符串规范化为纳秒时间戳time.clock负责结合用户时区提取本地小时二者与数据文档中的时段配置结合即可用寥寥几行 Rego 表达出清晰、可验证、默认拒绝的时间访问策略。这套解析 → 定位 → 比较的模式同样适用于会议时间、维护窗口、登录时段限制等更广泛的时间控制需求。赞分享后端认证鉴权云原生【免费下载链接】opaOpen Policy Agent (OPA) is an open source, general-purpose policy engine.项目地址https://gitcode.com/gh_mirrors/op/opa点击查看免费下载相关推荐重塑数字记忆WeChatMsg如何让对话数据真正属于你重塑数字记忆WeChatMsg如何让对话数据真正属于你 你是否曾为那些珍贵的微信对话无法永久保存而遗憾当重要的对话被误删当美好的回忆随着设备更换而消失你后端认证鉴权云原生在 Refine 中实现授权基于 Access Control Provider 的 RBAC/ABAC 权限控制指南在 Refine 中实现授权基于 Access Control Provider 的 RBAC/ABAC 权限控制指南 授权Authorization是前端企业应用以 OPA 作为 Envoy 外部授权服务用 Envoy、SPIFFE/SPIRE 与 Rego 策略构建微服务入口与服务间访问控制以 OPA 作为 Envoy 外部授权服务用 Envoy、SPIFFE/SPIRE 与 Rego 策略构建微服务入口与服务间访问控制 本文源自 OPA 官方博后端认证鉴权云原生上一篇如何用 tiny11builder 把 Windows 11 安装镜像缩小 42%下一篇Chainlink 测试运行器解析tools/test 如何用一条命令搞定单元测试、Postgres 自动化与 Flaky 测试诊断创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表