更好的外部授權
AuthorizationPolicy 現在支援 CUSTOM 動作,可將授權委派給外部系統。
背景
Istio 的授權策略為網格中的服務提供存取控制。它快速、強大且廣泛使用。自 Istio 1.4 首次發布以來,我們不斷改進,使策略更具彈性,包括 DENY
動作、排除語義、X-Forwarded-For
標頭支援、巢狀 JWT 宣告支援 等。這些功能提高了授權策略的靈活性,但仍有許多用例無法透過此模型支援,例如:
您有自己的內部授權系統,無法輕易遷移到授權策略,或無法輕易被其取代。
您想要與第三方解決方案(例如 Open Policy Agent 或
oauth2
proxy)整合,這可能需要使用 Istio 中的 低階 Envoy 設定 API,或者根本不可能實現。授權策略缺乏您用例所需的語義。
解決方案
在 Istio 1.9 中,我們透過引入 CUSTOM
動作,將可擴展性導入授權策略,讓您可以將存取控制決策委派給外部授權服務。
CUSTOM
動作讓您可以將 Istio 與實作自身自訂授權邏輯的外部授權系統整合。下圖顯示了這種整合的高階架構:
在配置時,網格管理員會配置具有 CUSTOM
動作的授權策略,以在代理 (gateway 或 sidecar) 上啟用外部授權。管理員應驗證外部授權服務已啟動並執行。
在執行時:
請求會被代理攔截,代理會將檢查請求傳送到外部授權服務,如使用者在授權策略中配置的那樣。
外部授權服務將決定是否允許該請求。
如果允許,該請求將繼續,並會強制執行由
ALLOW
/DENY
動作定義的任何本機授權。如果拒絕,該請求會立即被拒絕。
讓我們來看一個具有 CUSTOM
動作的授權策略範例:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ext-authz
namespace: istio-system
spec:
# The selector applies to the ingress gateway in the istio-system namespace.
selector:
matchLabels:
app: istio-ingressgateway
# The action "CUSTOM" delegates the access control to an external authorizer, this is different from
# the ALLOW/DENY action that enforces the access control right inside the proxy.
action: CUSTOM
# The provider specifies the name of the external authorizer defined in the meshconfig, which tells where and how to
# talk to the external auth service. We will cover this more later.
provider:
name: "my-ext-authz-service"
# The rule specifies that the access control is triggered only if the request path has the prefix "/admin/".
# This allows you to easily enable or disable the external authorization based on the requests, avoiding the external
# check request if it is not needed.
rules:
- to:
- operation:
paths: ["/admin/*"]
它參考網格設定中定義的供應商 my-ext-authz-service
。
extensionProviders:
# The name "my-ext-authz-service" is referred to by the authorization policy in its provider field.
- name: "my-ext-authz-service"
# The "envoyExtAuthzGrpc" field specifies the type of the external authorization service is implemented by the Envoy
# ext-authz filter gRPC API. The other supported type is the Envoy ext-authz filter HTTP API.
# See more in https://envoy.dev.org.tw/docs/envoy/v1.16.2/intro/arch_overview/security/ext_authz_filter.
envoyExtAuthzGrpc:
# The service and port specifies the address of the external auth service, "ext-authz.istio-system.svc.cluster.local"
# means the service is deployed in the mesh. It can also be defined out of the mesh or even inside the pod as a separate
# container.
service: "ext-authz.istio-system.svc.cluster.local"
port: 9000
CUSTOM
動作 的授權策略會在執行時啟用外部授權,可以使用您已在其他動作中使用的相同規則,根據請求有條件地觸發外部授權。
外部授權服務目前在 meshconfig
API 中定義,並以其名稱引用。它可以部署在網格中,無論是否有代理。如果有代理,您可以進一步使用 PeerAuthentication
在代理和您的外部授權服務之間啟用 mTLS。
CUSTOM
動作目前處於實驗階段;API 可能會根據使用者回饋以不向後相容的方式變更。授權策略規則目前在使用 CUSTOM
動作時不支援驗證欄位(例如來源主體或 JWT 宣告)。給定工作負載只允許一個供應商,但您仍然可以在不同的工作負載上使用不同的供應商。
如需更多資訊,請參閱更好的外部授權設計文件。
OPA 範例
在本節中,我們將示範如何將 CUSTOM
動作與 Open Policy Agent 一起用作入口網關上的外部授權器。我們將有條件地在除 /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 Pod 中的單獨容器,也可以完全部署在單獨的 Pod 中。
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpbin-with-opa
labels:
app: httpbin-with-opa
service: httpbin-with-opa
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin-with-opa
---
# Define the service entry for the local OPA service on port 9191.
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: local-opa-grpc
spec:
hosts:
- "local-opa-grpc.local"
endpoints:
- address: "127.0.0.1"
ports:
- name: grpc
number: 9191
protocol: GRPC
resolution: STATIC
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: httpbin-with-opa
labels:
app: httpbin-with-opa
spec:
replicas: 1
selector:
matchLabels:
app: httpbin-with-opa
template:
metadata:
labels:
app: httpbin-with-opa
spec:
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
- 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"
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
$ 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.local"
envoyExtAuthzGrpc:
service: "local-opa-grpc.local"
port: "9191"
apiVersion: v1
data:
mesh: |-
# Add the following contents:
extensionProviders:
- name: "opa.default"
envoyExtAuthzGrpc:
service: "opa.default.svc.cluster.local"
port: "9191"
使用 CUSTOM 動作建立 AuthorizationPolicy
執行以下命令以建立授權策略,該策略在除 /ip
之外的所有路徑上啟用外部授權。
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin-opa
spec:
selector:
matchLabels:
app: httpbin-with-opa
action: CUSTOM
provider:
name: "opa.local"
rules:
- to:
- operation:
notPaths: ["/ip"]
EOF
$ 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 策略
建立一個用戶端 Pod 來傳送請求
$ kubectl apply -f @samples/sleep/sleep.yaml@ $ export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
使用 OPA 簽署的測試 JWT 權杖
$ export TOKEN_PATH_HEADERS="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoiTDJobFlXUmxjbk09IiwibmJmIjoxNTAwMDAwMDAwLCJleHAiOjE5MDAwMDAwMDB9.9yl8LcZdq-5UpNLm0Hn0nnoBHXXAnK4e8RSl9vn6l98"
測試 JWT 權杖具有以下宣告:
{ "path": "L2hlYWRlcnM=", "nbf": 1500000000, "exp": 1900000000 }
path
宣告的值為L2hlYWRlcnM=
,這是/headers
的 base64 編碼。傳送沒有權杖的
/headers
路徑請求。這應該會因沒有 JWT 權杖而被拒絕,並出現 403 錯誤。$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin-with-opa:8000/headers -s -o /dev/null -w "%{http_code}\n" 403
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin:8000/headers -s -o /dev/null -w "%{http_code}\n" 403
傳送具有有效權杖的
/get
路徑請求。這應該會因路徑/get
與權杖/headers
不符而被拒絕,並出現 403 錯誤。$ 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
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin: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
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin:8000/headers -H "Authorization: Bearer $TOKEN_PATH_HEADERS" -s -o /dev/null -w "%{http_code}\n" 200
傳送沒有權杖的
/ip
路徑請求。這應該會因路徑/ip
被排除在授權之外而被允許,並出現 200 狀態。$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin-with-opa:8000/ip -s -o /dev/null -w "%{http_code}\n" 200
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin:8000/ip -s -o /dev/null -w "%{http_code}\n" 200
檢查代理和 OPA 日誌以確認結果。
摘要
在 Istio 1.9 中,授權策略中的 CUSTOM
動作讓您可以輕鬆地將 Istio 與任何外部授權系統整合,並具有以下優點:
在授權策略 API 中提供一流支援
易於使用:只需使用 URL 定義外部授權器,並透過授權策略啟用即可,無需再使用
EnvoyFilter
API 的麻煩。有條件觸發,可提高效能
支援外部授權器的各種部署類型
具有或不具有代理的正常服務和 Pod
在工作負載 Pod 內部作為單獨的容器
在網格外部
我們正在努力在後續版本中將此功能升級到更穩定的階段,並歡迎您在 discuss.istio.io 上提供回饋。
致謝
感謝 Craig Box
、Christian Posta
和 Limin Wang
審閱此部落格草稿。