使用场景
istio具有外部授权扩展,可以通过开发外部扩展的方式,到访问进行授权拦截,可参考官方的外部授权例子。但在实际应用中,外部授权已经存在了,或者是第三方的应用,不可能再重新开发了,这时使用官方的外部授权的例子已经适合了,但好在istio有好的扩展性,可以使用istio的EnvoyFilter的lua脚本功能,连接到外部授权。最近项目中有个需求,需要使用已经存在的外部服务提供istio的访问授权,查看了istio的EnvoyFilter配置实例,决定采用EnvoyFilter的lua扩展,通过lua扩展中的httpCall功能调用授权服务。lua http扩展就是envoy的扩展,详细参照这里。下面摘取httpCall()进行简单说明:
httpCall()
注意这个方法需要放到function envoy_on_request(request_handle)方法里,因为这个方法是request_handle的一个方法。
function envoy_on_request(request_handle)
-- 使用下面的头、正文和超时对上游主机做一个 HTTP 调用。
local headers, body = request_handle:httpCall(
"lua_cluster",
{
[":method"] = "POST",
[":path"] = "/",
[":authority"] = "lua_cluster"
},
"hello world",
5000)
-- 添加来自 HTTP 调用的信息到将要被发送到过滤器链中下一个过滤器的头。
request_handle:headers():add("upstream_foo", headers["foo"])
request_handle:headers():add("upstream_body_size", #body)
end
cluster 是一个字符串,映射为一个配置好的集群管理器。headers 一个要发送的键值对的表。注意,:method,:path 和 :authority 头必须被设置。body 是一个可选的要发送的正文数据的字符串。timeout
返回 headers,这是响应头的一个表。返回 body,这是响应正文的字符串。如果没有正文,则返回空。
有了这个方法,就为istio的扩展提供了无限可能,可以提供更复杂的业务逻辑控制,这些逻辑控制可以使用你所熟悉的任何语言,任何体系架构,只需要提供一个接口给httpCall,在httpCall调用前后,加上简单的lua逻辑代码,实现与外部授权(不光是授权,别的业务功能亦可)服务的交互对接。
实例应用
现在有这么一个需求,访问k8s apiserver时,需要进行精确的授权,如多租户及更精确的角色权限,还有创建k8s apiserver只有rolebindings,还有在创建各种k8s对象时,需要检查特定规则的labels是不合规,类似这样的场景在k8s本身提供的apiserver中比较难以做到,这都需要通过一个外围的应用进行控制。在访问k8s apiserver 时,经过istio提供的路由访问,配合httpCall,就可以调用授权服务,进行apiserver接口的各类业务控制了。
因本例子中apiserver是https,需要将https转http才能使用,详细可看我的另一往篇文章《istio的http访问https外部服务配置》,这里只贴出EnvoyFilter部分代码
apiVersion: /v1alpha3
kind: EnvoyFilter
metadata:
name: my-auth-ef
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": /envoy.extensions.filters.http.lua.v3.Lua
inlineCode: |
local auth_headers = {
[":method"] = "GET",
[":path"] = "/authCheck",
[":authority"] = "httpk8s.com"
}
local headers, body = request_handle:httpCall("lua_cluster",auth_headers,auth_body,5000)
--auth服务返回的是json string,这里需要JSON工具转成json对象
--任意找一个lua的JSON包引入或者贴到此代码的前面
local jBody = JSON.toJSON(body)
if jBody then
if jBody.code == 200 and jBody.token then
request_handle:headers():add("Authorization", jBody.token)
return
else
request_handle:respond({[":status"] = jBody.code},body)
end
end
request_handle:respond({[":status"] = 401},"Unauthorized")
end
- applyTo: CLUSTER
match:
context: ANY
cluster: {}
patch:
operation: ADD
value:
name: lua_cluster
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: lua_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: my-auth.myistio.svc.cluster.local
port_value: 8091
注意上面的cluster的address 是authentication service的地址