背景
istio 中的授权策略为网格内部的服务提供访问控制。授权策略是快速、强大及被广泛使用的功能,自istio 1.4首次发布以来,我们进行了持续改进,以使策略更加灵活,包含 DENY action, 排除语义, X-Forwarded-For 头支持, 嵌套 JWT claim 支持等,这些功能提高了授权策略的灵活性,但是此模型仍然不支持许多用例,例如:
您拥有自己的内部授权系统,该系统无法轻松迁移到授权策略或无法轻松地被其替换。
您想与第三方解决方案(例如,opa 或oauth2代理)集成,该解决方案可能需要使用Istio中的底层Envoy配置API,或者根本无法使用。
解决方案
在istio 1.9中,引入了 CUSTOM action来实现对授权策略的可扩展性,该操作使您可以将访问控制决策委派给外部授权服务。
CUSTOM action使您可以将Istio与实现其自己的自定义授权逻辑的外部授权系统集成。下图显示了此集成的高级体系结构:
在配置时,网格管理员使用一种CUSTOM action来配置授权策略,以在代理(网关或Sidecar)上启用外部授权。管理员应验证外部身份验证服务已启动并正在运行。
在运行时,
代理将拦截请求,代理将按照用户在授权策略中配置的方式将检查请求发送到外部身份验证服务。
如果允许,该请求将继续,并将由ALLOW/ DENYaction定义的任何本地授权强制执行。
让我们看一下带有该CUSTOM动作的示例授权策略:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ext-authz
namespace: istio-system
spec:
# 选择器适用于istio-system 命名空间中的入口网关。
selector:
matchLabels:
app: istio-ingressgateway
# CUSTOM action将访问控制委派给外部授权者,这与在代理内部强制执行访问控制的ALLOW / DENY操作不同。
action: CUSTOM
# 提供程序指定在meshconfig中定义的外部授权者的名称,该名称指示在何处以及如何与外部身份验证服务进行通信。
provider:
name: "my-ext-authz-service"
# 该规则指定仅当请求路径具有前缀"/admin/"时才触发访问控制。 这使您可以根据请求轻松启用或禁用外部授权,从而避免了不需要外部检查请求的情况。
rules:
- to:
- operation:
paths: ["/admin/*"]
引用了定义在mesh config中名为my-ext-authz-service 的提供者
extensionProviders:
# 授权策略在其提供者字段中引用名称"my-ext-authz-service"。
- name: "my-ext-authz-service"
# "envoyExtAuthzGrpc"字段指定由Envoy ext-authz过滤器gRPC API实现的外部授权服务的类型。 另一个受支持的类型是Envoy ext-authz filter HTTP API。
envoyExtAuthzGrpc:
# 服务和端口指定外部身份验证服务的地址,"ext-authz.istio-system.svc.cluster.local"表示该服务已部署在网格中。 也可以将其定义为网格之外,甚至可以将其定义为单独的容器。
service: "ext-authz.istio-system.svc.cluster.local"
port: 9000
CUSTOM action 授权策略使运行时启用外部授权,它可以被配置为根据使用您已经使用其他action同样的规则要求的外部授权进行触发。
外部授权服务当前在meshconfigAPI中定义,并通过其名称引用。它可以在有或没有代理的情况下部署在网格中。如果使用代理,则可以进一步用于PeerAuthentication在代理和外部授权服务之间启用mTLS。
该CUSTOM action目前处于实验阶段; API可能会根据用户反馈以非向后兼容的方式进行更改。该规则当前不支持与身份验证相关的字段(例如,source principal 或 JWT claim),并且给定工作负载仅允许一个提供程序,但是您仍可以在不同的工作负载上使用不同的提供程序。
OPA示例
在本节中,我们将演示如何将CUSTOM action与opa一起用作入口网关上的外部授权者。我们将有条件地在除/ip之外的所有路径上启用外部授权。
您也可以参考外部授权任务以获取使用示例ext-authz服务器的更基本的介绍
创建示例OPA 策略
运行以下命令,创建一个OPA策略,如果路径的前缀与JWT令牌中的声明"path"(base64编码)匹配,则允许该请求:
cat > policy.rego <<EOF
package envoy.authz
import input.attributes.request.http as http_request
default allow = false
token = {"valid": valid, "payload": payload} {
[_, encoded] := split(http_request.headers.authorization, " ")
[valid, _, payload] := io.jwt.decode_verify(encoded, {"secret": "secret"})
}
allow {
is_token_valid
action_allowed
}
is_token_valid {
token.valid
now := time.now_ns() / 1000000000
token.payload.nbf <= now
now < token.payload.exp
}
action_allowed {
startswith(http_request.path, base64url.decode(token.payload.path))
}
EOF
kubectl create secret generic opa-policy --from-file policy.rego
部署httpbin和OPA
启用sidecar注入
kubectl label ns default istio-injection=enabled
运行以下命令以部署示例应用程序httpbin和OPA。OPA可以作为单独的容器部署在httpbin容器中,也可以完全部署在单独的容器中:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: opa
labels:
app: opa
spec:
ports:
- name: grpc
port: 9191
targetPort: 9191
selector:
app: opa
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: opa
labels:
app: opa
spec:
replicas: 1
selector:
matchLabels:
app: opa
template:
metadata:
labels:
app: opa
spec:
containers:
- name: opa
image: openpolicyagent/opa:latest-envoy
securityContext:
runAsUser: 1111
volumeMounts:
- readOnly: true
mountPath: /policy
name: opa-policy
args:
- "run"
- "--server"
- "--addr=localhost:8181"
- "--diagnostic-addr=0.0.0.0:8282"
- "--set=plugins.envoy_ext_authz_grpc.addr=:9191"
- "--set=plugins.envoy_ext_authz_grpc.query=data.envoy.authz.allow"
- "--set=decision_logs.console=true"
- "--ignore=.*"
- "/policy/policy.rego"
ports:
- containerPort: 9191
livenessProbe:
httpGet:
path: /health?plugins
scheme: HTTP
port: 8282
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /health?plugins
scheme: HTTP
port: 8282
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: proxy-config
configMap:
name: proxy-config
- name: opa-policy
secret:
secretName: opa-policy
EOF
还要部署httpbin:
kubectl apply -f samples/httpbin/httpbin.yaml
定义外部授权者
运行以下命令以编辑meshconfig:
kubectl edit configmap istio -n istio-system
将以下内容添加extensionProviders到中meshconfig:
apiVersion: v1
data:
mesh: |-
# Add the following contents:
extensionProviders:
- name: "opa.default"
envoyExtAuthzGrpc:
service: "opa.default.svc.cluster.local"
port: "9191"
使用CUSTOM action创建AuthorizationPolicy
运行以下命令以创建授权策略,以在除/ip以下路径之外的所有路径上启用外部授权:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin-opa
spec:
selector:
matchLabels:
app: httpbin
action: CUSTOM
provider:
name: "opa.default"
rules:
- to:
- operation:
notPaths: ["/ip"]
EOF
测试OPA政策
$ kubectl apply -f samples/sleep/sleep.yaml
$ export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
$ export TOKEN_PATH_HEADERS="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoiTDJobFlXUmxjbk09IiwibmJmIjoxNTAwMDAwMDAwLCJleHAiOjE5MDAwMDAwMDB9.9yl8LcZdq-5UpNLm0Hn0nnoBHXXAnK4e8RSl9vn6l98"
{
"path": "L2hlYWRlcnM=",
"nbf": 1500000000,
"exp": 1900000000
}
path claim 要求有值L2hlYWRlcnM=,其是/headers的BASE64编码。
将请求发送到/headers没有令牌的路径。应该使用403拒绝它,因为没有JWT令牌:
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin-with-opa:8000/headers -s -o /dev/null -w "%{http_code}\n"
403
/get使用有效令牌向路径发送请求。这应该用403拒绝,因为路径/get与令牌不匹配/headers:
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin-with-opa:8000/get -H "Authorization: Bearer $TOKEN_PATH_HEADERS" -s -o /dev/null -w "%{http_code}\n"
403
/headers使用有效令牌向路径发送请求。应该使用200,因为路径与令牌匹配:
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin-with-opa:8000/headers -H "Authorization: Bearer $TOKEN_PATH_HEADERS" -s -o /dev/null -w "%{http_code}\n"
200
将请求发送到/ip没有令牌的路径。应该允许使用200,因为该路径/ip已排除在授权范围之外:
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin-with-opa:8000/ip -s -o /dev/null -w "%{http_code}\n"
200
概括
在Istio 1.9中,CUSTOM授权策略中的action使您可以轻松地将Istio与任何外部授权系统集成,具有以下优点:
易用性:只需使用URL定义外部授权者,并使用授权策略启用,再也不用担心EnvoyFilterAPI
扫描关注我: