Envoy JWT RBAC ABAC OPA
JWT:json web token
JWS令牌格式:Header.Payload.Signature
Envoy JWT:
1、Envoy基于JWTAuthentication过滤器完成终端用户认证:
基于过滤器核验JWT的签名、受众和颁发者以确定其携带有有效的令牌;若验证失败则请求被拒绝;
支持在请求的各种条件下检查JWT,例如仅针对特定路径;
支持从请求的各个位置提取JWT,并可合并同一请求中的多个JWT需求;
JWT签名所需的JWKS(JsonWebKeySet)可在过滤器配置中内联指定,也可通过HTTP/HTTPS从远程服务器获取
2、配置时,需要使用名称envoy.filters.http.jwt_authn配置此过滤器,主要由两个字段
providers:定义如何验证JWT,例如提取令牌的位置、获取公钥的位置以及输出有效负载的位置等;
rules:定义匹配规则及相关要求;
Envoy JWT过滤器配置语法:
http_filters: - name: envoy.filters.http.jwt_authn config: providers: [] # 定义provider名称及其相关的属性, 以指定如何验证JWT; name: ... # provider名称; issuer: ... # JWT的签发者, 通常是一个URL或一个email; audiences: [] # 允许访问的JWT受众列表, 含有此处指定的audiences其中之一的JWT即可被接受; remote_jwks: {...} # 可通过HTTP/HTTPS远程获取到的JWK, 因此内嵌的最重要字段即为uri; local_jwks: {...} # 本地数据源可以访问到的JWK, 支持通过本地文件或inline_string方式加载; from_headers: {...} # 定义从通过哪个HTTP协议标头获取JWT, 例如“Authorization: Bear <token>”; from_params: {...} # 定义从哪个URL param中获取JWT; forward: ... # 布尔型值, 定义是否无需在认证成功后从请求中移除JWT, 默认为false, 即需要移除; forward_payload_header: ... # 指定用于将经过验证的JWT的payload转发至后端的标头; rules: [] # 定义特定路由条件下的JWT验证要求; - match: {...} # 路由匹配条件 requires: {...} # JWT验证要求, 以下验证方式仅能使用其中一种; provider_name: ... # 需要的provider名称; provider_and_audiences: {...} # 需要的provider和audiences; requires_any: {...} # 由requirements参数指定需要的providers列表, 其中任何一个provider验证通过, 结果即为通过; requires_all: {...} # 由requirements参数指定需要的providers列表, 其中所有的provider验证通过, 结果才为通过; allow_missing_or_failed: {...} # 即便JWT缺失或验证失败,结果依然通过;
JWT认证配置示例:
下面的示例定义对于全站请求进行JWT认证, 它仅认可由jwt_provider签发的books.read audience。
local_jwks指定了以inline_string的方式加载JWK配置。
http_filters: - name: envoy.filters.http.jwt_authn config: providers: jwt_provider: jissuer: test local_jwks: inline_string: "{\"keys\":[{\"use\": \"sig\",\"alg\": \"RS256\",\"kid\": \"test\",\"kty\": \"RSA\",\"n\": \"4f5wg5l2hKsTeNem_V41fGnJm6gOdrj8ym3rFkEU_wT8RDtnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0icqabvJOMv QtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhCPUIIZOQn_MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR-1DcKJzQBSTAGnpYVaqpsARapnwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKARdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7w\",\"e\": \"AQAB\"}]}" forward: true forward_payload_header: "plain-authorization" rules: - match: prefix: / requires: provider_and_audiences: provider_name: jwt_provider audiences: books.read
RBAC
RBAC是一种操作授权机制,用于界定“谁(Subject)”能够“操作(Verb)”哪个或哪类“对象(Object)
Envoy的RBAC过滤器为服务提供服务级别和方法级别的访问控制功能,相关过滤器配置名称为envoy.filters.http.rbac
1、该过滤器支持基于连接属性(IP、Port或SSLSubject)以及传入的请求的HTTP标头安全列表(Allow)或阻止列表(Deny)策略集进行配置;
2、支持强制模式和影子模式,影子模式仅用于验证策略而不会产生真正的影响Envoy的RBAC配置主要由两个参数组成。
1、action:策略匹配时要采取的操作,当且仅当以下情形方才允许请求的操作。
action为允许,且至少有一个策略匹配。
action为拒绝,但没有任何策略匹配。
2、policies:从策略名称到策略的映射,成功的条件是至少一个策略与请求匹配。
RBAC配置器过滤语法:
action: ... # 策略匹配时的操作行为, 支持ALLOW和DENY两个; policies: {...} # 授权策略 ROLE_NAM # 角色名称 permissions: [] # 应用于一个角色之上的权限许可列表, 各列表项之间为“或”关系; any: ... # 布尔型值, 是否匹配所有操作; header: {...} # 核验传入的HTTP请求报文的指定标头; 仅适用于HTTP请求; destination_ip: {...} # 针对于目标IP的CIDR地址块的操作权限; destination_port: {...} # 针对于目标端口的操作权限; metadata: {...} # 针对于指定的元数据的操作权限; requested_server_name: ... # 针对于客户端请求的目标服务器的操作权限; and_rules: {...} # 以“与”关系定义的一组操作权限; or_rules: {...} # 以“或”关系定义的一组操作权限; not_rules: {...} # 以“非”关系定义的一组操作权限; principals: [] authenticated: {...} # 经过认证的; header: {...} # 传入HTTP请求报文的指定标头; metadata: {...} # 描述有关Subject的其它信息的元数据; and_ids: {...} # “与”关系的一组主体; or_ids: {...} # “或”关系的一组主体; not_id: {...} # “非”关系主体, 即指定主体之外的其他主体;
RBAC配置示例:
下面是一个许可权限示例,它定义了service-admin和product-viewer两个role。
service-admin:经过认证的Service账号admin和superuser拥有全部操作权限。
其它任何用户仅可于端口80或443之上针对/products起始的URL执行GET请求方法。
action: ALLOW policies: "service-admin": permissions: - any: true principals: - authenticated: principal_name: exact: "cluster.local/ns/default/sa/admin" - authenticated: principal_name: exact: "cluster.local/ns/default/sa/superuser" "product-viewer": permissions: - and_rules: rules: - header: { name: ":method", exact_match: "GET" } - header: { name: ":path", regex_match: "/products(/.*)?" } - or_rules: rules: - destination_port: 80 - destination_port: 443 principals: - any: true
ABAC
ABAC:policy-based access control,将属性组合为策略来授予用户访问权限。可基于内容、时间、位置、IP等进行授权检查。Envoy不支持ABAC需要借助扩展接入外部授权检查器,如通过envoy.ext_authz来引入OPA,OPA是一个比较通用的来实现ABAC策略引擎,需要用户自己定义权限和数据等信息。
OPA:Open Policy Agent
开源的通用策略引擎,用于统一整个堆栈中的策略应用,使用高级声明性语言Rego(也称为policy language)定义策略,常用于在微服务、Kubernetes、CI/CDpipeline、API网关等中实施策略。
外部授权(envoy.ext_authz)
外部授权( External Authorization)即调用第三方的授权服务来核验用户权限的机制。
Envoy通过外部授权过滤器调用外部的授权服务以检查传入的请求是否已经获取授权。
此过滤器可以配置为网络过滤器( config.filter.network.ext_authz.v2.ExtAuthz),也可以配置为HTTP过滤器( config.filter.http.ext_authz.v2.ExtAuthz)甚至是二者的组合。
若是网络过滤器核验授权失败,则直接关闭该连接。
若是HTTP过滤器核验授权失败,则响应为403( Forbidden)。
外部授权服务通常应该定义为上游集群。
收到请求并核验其授权时,若外部授权服务不可用, 请求是否能获得授权则取决于过滤器的failure_mode_allow参数的配置;
OPA:open policy agent
OpenPolicyAgent(简称OPA)是一款go语言编写的开源通用策略引擎,它通过高级声明式语言rego编写策略代码为应用程序实现细粒度的访问控制机制,可用于为微服务、Kubernetes、CI/CDpipeline和PIGateway 等应用场景实施策略机制;
OPA可以同微服务一起部署为独立运行的服务,例如以sidecar形式运行;
1、通常,出于保护应用程序的目的,发往微服务的每个请求都需要获得授权后才能进行处理
2、而为了检查授权,微服务则需要对OPA服务发出API调用,以确定收到的请求是否被授权;但策略的执行需要由应用程序完成,例如某HTTP请求被策略拒绝时,应用程序需要响应以“HTTP403Forbidden”。
OPA运行方式有两种:
1、sidecar方式
2、独立的服务,如istio中的mixer,每个请求都要发起外部调用,性能差,所以在istio中一般不开启opa。
OPA决策机制:
OPA决策机制在OPA中,决策过程依赖于三个输入:
1、Query:查询输入,它会触发决策过程查询数据指定了OPA应该决策的事情,它必须格式化为JSON格式,例如,对于“是否允许用户Alice调用“GET/resrources”这个问题,查询输入包含了Alice、GET和/resources三个参数。
2、Data:OPA进行决策时需要参考的适配于特定环境的事实定义例如,需要决策将Pod调度至哪个Kubernetes集群节点,数据应该是可用节点及相应容量的列表Data同样必须以JSON格式提供,且可能会随时间而变化,OPA会将其最新状态缓存在内存中
3、Policy:指定了决策计算逻辑,对于给定的Data和Query,该计算逻辑会产生策略决定,即查询结果
但OPA只是策略引擎,策略需要由用户自己定义:
策略引擎负责解释Policy中包含的规则,并基于Data和Query做出策略决策,并将决策结果格式化为JSON格式进行输出
OPA策略示例:
如下策略定义了哪些客户端主机能够访问哪些path策略,默认策略为deny,allow{}用于定义授权机制
package envoy.authz import input.attributes.request.http as http_request import input.attributes.source.address as source_address default allow = false allowed_paths = {"/hello", "/the/good/path", "/the/bad/path"} allowed_local_paths = {"/good/backend", "/good/db"} # allow access to the Web service from the subnet 172.28.0.0/16 for the allowed paths allow { allowed_paths[http_request.path] http_request.method == "GET" net.cidr_contains("172.28.0.0/16", source_address.Address.SocketAddress.address) } # allow Web service access from localhost for locally allowed paths allow { source_address.Address.SocketAddress.address == "127.0.0.1" allowed_local_paths[http_request.path] http_request.method == "GET" }