Istio安全-授權
授權HTTP流量
本節展示如何在istio網格中授權HTTP流量。
部署Bookinfo。由於下例在策略中使用了principal和namespace,因此需要啟用mutual TLS。
為使用HTTP流量的負載配置訪問控制
本任務展示了如何使用istio的授權設置訪問控制。首先,使用簡單的deny-all
策略拒絕所有到負載的請求,然后增量地授權到負載的訪問。
下面使用kubernetes的service account授權istio網格中的HTTP訪問
-
在
default
命名空間中創建deny-all
策略。該策略沒有selector
字段,將會應用到default命名空間中的所有負載上。sepc:
字段為空{}
,意味着禁止所有的流量。$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-all namespace: default #禁止任何對default命名空間中的負載的訪問 spec: {} EOF
刷新Boofinfo
productpage
的頁面,可以看到錯誤RBAC: access denied
,即deny-all
策略已經生效,且istio沒有其他規則允許流量訪問網格中的負載。 -
執行如下命令創建一個
productpage-viewer
允許使用GET
方法訪問productpage
負載。該策略並沒有設置from字段,意味着允許所有用戶和工作負載進行訪問:$ kubectl apply -f - <<EOF apiVersion: "security.istio.io/v1beta1" kind: "AuthorizationPolicy" metadata: name: "productpage-viewer" namespace: default spec: selector: matchLabels: app: productpage #允許對default命名空間中的負載productpage的GET訪問 rules: - to: - operation: methods: ["GET"] EOF
再次刷新Boofinfo
productpage
的頁面,此時可以看到Bookinfo Sample
頁面,但該頁面同時也顯示了如下錯誤:Error fetching product details
Error fetching product reviews
這些錯誤符合預期,因為並沒給
productpage
負載授權訪問details
和reviews
負載。下面,需要配置一個策略來授權訪問這些負載。 -
執行如下命令創建
details-viewer
策略來允許productpage
負載使用cluster.local/ns/default/sa/bookinfo-productpage
service account發起GET
請求訪問details
:在
productpage
的deployment中可以看到它使用了service accountbookinfo-productpage
$ kubectl apply -f - <<EOF apiVersion: "security.istio.io/v1beta1" kind: "AuthorizationPolicy" metadata: name: "details-viewer" namespace: default spec: selector: matchLabels: app: details rules: - from: - source: principals: ["cluster.local/ns/default/sa/bookinfo-productpage"] #允許使用default命名空間中的sa bookinfo-productpage訪問details to: - operation: methods: ["GET"] EOF
-
執行如下命令創建
reviews-viewer
策略來允許productpage
負載使用cluster.local/ns/default/sa/bookinfo-productpage
service account發起GET
請求訪問reviews
:$ kubectl apply -f - <<EOF apiVersion: "security.istio.io/v1beta1" kind: "AuthorizationPolicy" metadata: name: "reviews-viewer" namespace: default spec: selector: matchLabels: app: reviews rules: - from: - source: principals: ["cluster.local/ns/default/sa/bookinfo-productpage"] #允許使用default命名空間中的sa bookinfo-productpage訪問reviews,信任域為cluster.local to: - operation: methods: ["GET"] EOF
刷新Bookinfo
productpage
,可以在Bookinfo Sample
頁面的左邊看到Book Details
,並在頁面右側看到Book Reviews
,但在Book Reviews
一欄可以看到錯誤Ratings service currently unavailable
。這是因為
reviews
負載沒有權限訪問ratings
負載。為了解決這個問題, 需要授權reviews
負載訪問ratings
負載。下面配置一個策略來授權reviews
負載進行訪問。 -
運行下面命令創建策略
ratings-viewer
來允許reviews
負載使用cluster.local/ns/default/sa/bookinfo-reviews
service account發起GET
請求訪問ratings
:$ kubectl apply -f - <<EOF apiVersion: "security.istio.io/v1beta1" kind: "AuthorizationPolicy" metadata: name: "ratings-viewer" namespace: default spec: selector: matchLabels: app: ratings rules: - from: - source: principals: ["cluster.local/ns/default/sa/bookinfo-reviews"] to: - operation: methods: ["GET"] EOF
刷新Bookinfo
productpage
頁面,可以在Book Reviews
一欄中看到黑色和紅色的ratings信息。
卸載
$ kubectl delete authorizationpolicy.security.istio.io/deny-all
$ kubectl delete authorizationpolicy.security.istio.io/productpage-viewer
$ kubectl delete authorizationpolicy.security.istio.io/details-viewer
$ kubectl delete authorizationpolicy.security.istio.io/reviews-viewer
$ kubectl delete authorizationpolicy.security.istio.io/ratings-viewer
授權TCP流量
本節展示如何授權istio網格中的TCP流量。
部署
-
在同一個命名空間
foo
中部署兩個名為sleep
和tcp-echo
的負載,兩個負載都允許了Envoy代理。tcp-echo
負載監聽端口9000,9001和9002,並回顯收到的所有帶有前綴hello的流量。例如,如果發送"world"到tcp-echo
,則會響應"hello world"。tcp-echo
kubernetes service對象僅聲明端口9000和9001,並忽略端口9002。pass-through過濾器將處理端口9002的流量。使用如下命令部署命名空間和負載:openshift注意創建NetworkAttachmentDefinition
$ kubectl create ns foo $ kubectl apply -f <(istioctl kube-inject -f samples/tcp-echo/tcp-echo.yaml) -n foo $ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
-
使用如下命令校驗
sleep
可以通過9000和9001端口連接tcp-echo
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9000" | nc tcp-echo 9000' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' hello port 9000 connection succeeded
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9001" | nc tcp-echo 9001' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' hello port 9001 connection succeeded
-
校驗
sleep
可以連接tcp-echo
的9002端口。此時需要通過tcp-echo的pod IP發送流量,這是因為tcp-echo的kubernetes service對象中並沒有定義9002端口。獲取pod IP並使用如下命令發送請求:# TCP_ECHO_IP=$(kubectl get pod "$(kubectl get pod -l app=tcp-echo -n foo -o jsonpath={.items..metadata.name})" -n foo -o jsonpath="{.status.podIP}") # kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c "echo \"port 9002\" | nc $TCP_ECHO_IP 9002" | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' [root@bastion istio-1.7.0]# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c "echo \"port 9002\" | nc $TCP_ECHO_IP 9002" | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' hello port 9002 connection succeeded
可以看到tcp-echo的k8s service僅暴露了9000和9001端口:
# oc get svc -n foo NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE sleep ClusterIP 10.84.92.66 <none> 80/TCP 4m31s tcp-echo ClusterIP 10.84.85.246 <none> 9000/TCP,9001/TCP 4m32s
配置TCP負載的訪問控制
-
為
foo
命名空間中的tcp-echo
負載創建tcp-policy
授權策略,運行如下命令創建一個授權策略,允許到9000和9001的請求:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: tcp-policy namespace: foo spec: selector: matchLabels: app: tcp-echo #對匹配標簽的負載允許到端口9000/9001的訪問 action: ALLOW rules: - to: - operation: ports: ["9000", "9001"] EOF
-
使用如下命令校驗允許到9000的訪問
此時即使沒有上面的策略,到9000/9001的訪問都是允許的。因為istio默認使用寬容模式,區別是如果該服務暴露的不止9000/9001端口,那么其他端口是無法訪問的。
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9000" | nc tcp-echo 9000' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' hello port 9000 connection succeeded
-
使用如下命令校驗允許到9001的訪問
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9001" | nc tcp-echo 9001' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' hello port 9001 connection succeeded
-
校驗到9002的請求是拒絕的。這是由授權策略執行的,該策略也適用於pass through過濾器鏈,即使
tcp-echo
kubernetes service對象沒有明確聲明該port。# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c "echo \"port 9002\" | nc $TCP_ECHO_IP 9002" | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' command terminated with exit code 1 connection rejected
-
使用如下命令將策略更新為僅在9000端口上允許HTTP的GET方法
$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: tcp-policy namespace: foo spec: selector: matchLabels: app: tcp-echo action: ALLOW rules: - to: - operation: methods: ["GET"] ports: ["9000"] EOF
-
校驗到9000的端口的請求被拒絕了。這是因為此時規則僅允許HTTP格式的TCP流量。istio會忽略無效的ALLOW規則。最終結果是由於請求不匹配任何ALLOW規則,而被被拒絕。執行如下命令進行校驗:
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9000" | nc tcp-echo 9000' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' connection rejected
-
校驗到9001的請求被拒絕了。原因同樣是因為請求並沒有匹配任何ALLOW規則
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9001" | nc tcp-echo 9001' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' connection rejected
-
使用如下命令將策略更新為DENY,拒絕到9000的請求(當然此時其他端口會走默認的寬容模式)
$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: tcp-policy namespace: foo spec: selector: matchLabels: app: tcp-echo action: DENY rules: - to: - operation: methods: ["GET"] ports: ["9000"] EOF
-
校驗到9000端口的請求被拒絕。它與上面無效的ALLOW規則(istio忽略了整個規則)不同,istio忽略了僅支持HTTP的字段
methods
,但使用了ports
,導致匹配到這個端口的請求被拒絕:# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9000" | nc tcp-echo 9000' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' connection rejected
-
校驗到端口9001的請求是允許的,這是因為請求並不匹配DENY策略的
ports
字段# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- sh -c 'echo "port 9001" | nc tcp-echo 9001' | grep "hello" && echo 'connection succeeded' || echo 'connection rejected' hello port 9001 connection succeeded
卸載
$ kubectl delete namespace foo
action字段中的默認值為ALLOW,且ALLOW中的規則的關系是
AND
,而DENY中的規則的關系是OR
使用JWT進行授權
本任務展示如何設置istio授權策略來執行基於JSON Web Token(JWT)的訪問。Istio授權策略同時支持字符串類型和字符串列表類型的JWT claims。
部署
在foo
命名空間中部署部署兩個負載:httpbin
和sleep
。兩個負載都運行了Envoy代理。使用如下命令進行部署:
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
校驗可以使用sleep
連接httpbin
:
$ kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
200
使用有效的JWT和列表類型的claims允許請求
-
使用如下命令在
foo
命名空間中為httpbin
負載創建jwt-example
請求認證策略。該策略會讓httpbin
負載接受一個由testing@secure.istio.io
發布的JWT。$ kubectl apply -f - <<EOF apiVersion: "security.istio.io/v1beta1" kind: "RequestAuthentication" metadata: name: "jwt-example" namespace: foo spec: selector: matchLabels: app: httpbin jwtRules: - issuer: "testing@secure.istio.io" jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.7/security/tools/jwt/samples/jwks.json" EOF
jwksUri為開放公鑰的接口地址,用於獲取公鑰,進而對token進行校驗
-
校驗帶無效JWT的請求被拒絕了:
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -s -o /dev/null -H "Authorization: Bearer invalidToken" -w "%{http_code}\n" 401
-
校驗不帶JWT的請求是允許的,因為此時沒有配置授權策略
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -s -o /dev/null -w "%{http_code}\n" 200
-
下面命令會給
foo
命名空間中的httpbin
負載創建require-jwt
授權策略。該策略會要求所有的請求都必須包含一個有效的JWT(requestPrincipal
為testing@secure.istio.io/testing@secure.istio.io
)。Istio通過將JWT token的iss
和sub
與一個/分隔符組合來構造requestPrincipal
:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: require-jwt namespace: foo spec: selector: matchLabels: app: httpbin action: ALLOW rules: - from: - source: requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"] EOF
-
獲取key為
iss
和sub
,且值(testing@secure.istio.io
)相同的JWT。這會導致istio生成屬性requestPrincipal
,對應值為testing@secure.istio.io/testing@secure.istio.io
:# TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.7/security/tools/jwt/samples/demo.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode - {"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"testing@secure.istio.io","sub":"testing@secure.istio.io"}
-
校驗帶有效JWT的請求是允許的:
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -s -o /dev/null -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n" 200
-
校驗帶無效JWT的請求被拒絕:
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -s -o /dev/null -w "%{http_code}\n" 403
-
如下命令會更新
require-jwt
授權策略,要求JWT具有一個名為groups
的claim,且置為group1
:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: require-jwt namespace: foo spec: selector: matchLabels: app: httpbin action: ALLOW rules: - from: - source: requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"] when: - key: request.auth.claims[groups] values: ["group1"] EOF
-
獲取將
groups
claim設置為字符串列表的JWT:group1
和group2
:# TOKEN_GROUP=$(curl https://raw.githubusercontent.com/istio/istio/release-1.7/security/tools/jwt/samples/groups-scope.jwt -s) && echo "$TOKEN_GROUP" | cut -d '.' -f2 - | base64 --decode - {"exp":3537391104,"groups":["group1","group2"],"iat":1537391104,"iss":"testing@secure.istio.io","scope":["scope1","scope2"],"sub":"testing@secure.istio.io"}
-
校驗在請求的JWT的
groups
claim中帶group1
是允許的:# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -s -o /dev/null -H "Authorization: Bearer $TOKEN_GROUP" -w "%{http_code}\n" 200
-
校驗請求的JWT中沒有
groups
claim是被拒絕的# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -s -o /dev/null -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n" 403
卸載
$ kubectl delete namespace foo
總結
RFC 7519.定義了JWT token的格式。Istio的JWT認證流程使用了OAuth 2.0 和OIDC 1.0,可以使用jwks字段或jwksUri
字段標識公鑰的提供方。
AuthorizationPolicy
rule 規則中與 JWT 相關的字段包括:
field | sub field | JWT claims |
---|---|---|
from.source | requestPrincipals | iss/sub |
from.source | notRequestPrincipals | iss/sub |
when.key | request.auth.principal | iss/sub |
when.key | request.auth.audiences | aud |
when.key | request.auth.presenter | azp |
when.key | request.auth.claims[key] | JWT 全部屬性 |
參考
使用deny action的授權策略
本節將展示如何授權istio授權策略來拒絕istio網格中的HTTP流量。
部署
部署sleep應用並校驗連通性:
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
200
明確拒絕一個請求
-
下面命令會為
foo
命名空間中的httpbin
負載創建deny-method-get
授權策略。策略會對滿足rules
字段中的條件的請求執行DENY
action
。下面例子會拒絕使用GET
方法的請求$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-method-get namespace: foo spec: selector: matchLabels: app: httpbin action: DENY rules: - to: - operation: methods: ["GET"] EOF
-
校驗GET請求被拒絕:
# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/get" -X GET -s -o /dev/null -w "%{http_code}\n" 403
-
校驗允許
POST
請求# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/post" -X POST -s -o /dev/null -w "%{http_code}\n" 200
-
更新
deny-method-get
授權策略,只有當HTTP首部x-token
的值不為admin
時才會拒絕GET
請求。下面例子將notValues
字段設置為["admin"]
來拒絕首部字段不為admin
的請求。$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-method-get namespace: foo spec: selector: matchLabels: app: httpbin action: DENY rules: - to: - operation: methods: ["GET"] when: - key: request.headers[x-token] notValues: ["admin"] EOF
-
校驗當HTTP首部字段為
x-token: admin
時是允許的# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/get" -X GET -H "x-token: admin" -s -o /dev/null -w "%{http_code}\n" 200
-
校驗當HTTP首部字段為
x-token: guest
時是拒絕的# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/get" -X GET -H "x-token: guest" -s -o /dev/null -w "%{http_code}\n" 403
-
下面命令會創建
allow-path-ip
授權策略來允許請求通過/ip
路徑訪問httpbin
負載。通過在action
字段設置ALLOW
實現$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: allow-path-ip namespace: foo spec: selector: matchLabels: app: httpbin action: ALLOW rules: - to: - operation: paths: ["/ip"] EOF
-
校驗HTTP首部為
x-token: guest
且路徑為/ip
的GET請求仍然被deny-method-get
策略拒絕# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -X GET -H "x-token: guest" -s -o /dev/null -w "%{http_code}\n" 403
-
校驗HTTP首部為
x-token: admin
且路徑為/ip
的GET請求仍然被allow-path-ip
策略允許# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/ip" -X GET -H "x-token: admin" -s -o /dev/null -w "%{http_code}\n" 200
-
校驗HTTP首部為
x-token: admin
且路徑為/get
的GET請求仍然被拒絕,因為不匹配allow-path-ip
策略# kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/get" -X GET -H "x-token: admin" -s -o /dev/null -w "%{http_code}\n" 403
總結
本節主要展示了授權中的action
字段的用法:DENY
總是優先於ALLOW
,且ALLOW
中的規則的關系是AND
,而DENY
中的規則的關系是OR
。
卸載
$ kubectl delete namespace foo
授權ingress Gateway
本節展示如何在istio ingress網關上使用授權策略配置訪問控制。
istio授權策略支持基於IP的allow/deny列表,以及先前由Mixer策略支持的基於屬性的allow/deny列表。Mixer策略已經在1.5版本中被廢棄,不建議生產使用。
部署
在foo
命名空間中部署一個httpbin
負載,使用istio ingress網關暴露該服務:
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin-gateway.yaml) -n foo
通過設置externalTrafficPolicy:local
來更新ingress網關,使用以下命令在ingress網關上保留原始客戶端的源IP。更多參見 Source IP for Services with Type=NodePort
使用
externalTrafficPolicy:local
時,kube-proxy僅會將請求代理到本地endpoint,不會跨節點。如果沒有匹配的endpoint,則該會丟棄該報文,此時會保留報文的原始源IP地址(跨節點會使用SNAT將報文原始源IP地址修改為本節點地址)。
$ kubectl patch svc istio-ingressgateway -n istio-system -p '{"spec":{"externalTrafficPolicy":"Local"}}'
獲取INGRESS_HOST
和 INGRESS_PORT
$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
$ export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
校驗可以通過ingress網關訪問httbin負載
# curl "$INGRESS_HOST":"$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
200
使用如下命令的輸出來保證ingress網關接收到了原始客戶端的源IP地址,該地址將會在授權策略中使用:
# CLIENT_IP=$(curl "$INGRESS_HOST":"$INGRESS_PORT"/ip -s | grep "origin" | cut -d'"' -f 4) && echo "$CLIENT_IP"
172.20.127.78
基於IP的allow列表和deny列表
-
下面命令會為istio ingress網關創建授權策略
ingress-policy
。下面策略中的action
字段為ALLOW
,允許ipBlocks字段指定的IP地址訪問ingress網關。不在該列表中的IP地址會被拒絕。ipBlocks支持單IP地址和CIDR:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: ingress-policy namespace: istio-system spec: selector: matchLabels: app: istio-ingressgateway action: ALLOW rules: - from: - source: ipBlocks: ["1.2.3.4", "5.6.7.0/24"] #允許訪問網關的源地址IP列表 EOF
-
校驗到ingress網關的請求被拒絕了
# curl "$INGRESS_HOST":"$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n" 403
-
更新
ingress-policy
,在ALLOW IP地址列表中包含客戶端的IP地址$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: ingress-policy namespace: istio-system spec: selector: matchLabels: app: istio-ingressgateway action: ALLOW rules: - from: - source: ipBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"] EOF
-
校驗到ingress網關的請求變為允許
# curl "$INGRESS_HOST":"$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n" 200
-
更新
ingress-policy
授權策略,將action
設置為DENY
,將客戶端地址設置到ipBlocks
字段中,此時對ingress的源地址為$CLIENT_IP
的訪問將會被拒絕:$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: ingress-policy namespace: istio-system spec: selector: matchLabels: app: istio-ingressgateway action: DENY rules: - from: - source: ipBlocks: ["$CLIENT_IP"] EOF
-
校驗到ingress網關的請求被拒絕
# curl "$INGRESS_HOST":"$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n" 403
卸載
$ kubectl delete namespace foo
$ kubectl delete authorizationpolicy ingress-policy -n istio-system
總結
從上面可以看出,在用法上,對ingress網關的授權策略和對其他istio網關內部的服務的授權策略並沒有什么不同。
授權策略信任域遷移
本節展示如何在不修改授權策略的前提下進行信任域的遷移。
在istio 1.4中,引入了一個alpha特性來支持對授權策略的信任域的遷移,即如果一個istio網格需要改變其信任域時,則不需要手動修改授權策略。在istio中,如果一個負載運行在foo
命名空間中,使用的service account為bar
,系統的信任域為my-td
,則負載的身份標識為 spiffe://my-td/ns/foo/sa/bar
。默認情況下,istio網格的信任域為cluster.local
(除非在安裝時指定了其他域)。
部署
-
使用用戶信任域安裝istio,並啟用mutual TLS
# istioctl install -f cni-annotations.yaml --set values.global.istioNamespace=istio-system --set values.gateways.istio-egressgateway.enabled=true --set meshConfig.accessLogFile="/dev/stdout" --set values.global.trustDomain=old-td
-
在
default
命名空間中部署httpbin,並在default
和sleep-allow
命名空間中部署sleep$ kubectl label namespace default istio-injection=enabled $ kubectl apply -f samples/httpbin/httpbin.yaml $ kubectl apply -f samples/sleep/sleep.yaml $ kubectl create namespace sleep-allow $ kubectl label namespace sleep-allow istio-injection=enabled $ kubectl apply -f samples/sleep/sleep.yaml -n sleep-allow
-
配置如下授權策略,拒絕除
sleep-allow
命名空間中的sleep
外的對httpbin
的請求。$ kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: service-httpbin.default.svc.cluster.local namespace: default spec: rules: - from: - source: principals: - old-td/ns/sleep-allow/sa/sleep #只有sleep-allow命名空間中的sleep才能訪問httpbin服務 to: - operation: methods: - GET selector: matchLabels: app: httpbin --- EOF
校驗default
中的sleep
到httpbin
的請求,該請求被拒絕
# kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl http://httpbin.default:8000/ip -s -o /dev/null -w "%{http_code}\n"
403
校驗sleep-allow
中的sleep
到httpbin
的請求,該請求被允許
# kubectl exec "$(kubectl -n sleep-allow get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -n sleep-allow -- curl http://httpbin.default:8000/ip -s -o /dev/null -w "%{http_code}\n"
200
不使用信任域別名遷移信任域
-
使用新的信任域安裝istio,現在istio網格的信任域為
new-td
# istioctl install -f cni-annotations.yaml --set values.global.istioNamespace=istio-system --set values.gateways.istio-egressgateway.enabled=true --set meshConfig.accessLogFile="/dev/stdout" --set values.global.trustDomain=new-td
-
重新部署httpbin和sleep,使其接收來自新的istio控制面的變更
$ kubectl delete pod --all $ kubectl delete pod --all -n sleep-allow
-
校驗
default
和sleep-allow
命名空間的sleep
到httpbin
的請求都被拒絕了# kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl http://httpbin.default:8000/ip -s -o /dev/null -w "%{http_code}\n" 403 # kubectl exec "$(kubectl -n sleep-allow get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -n sleep-allow -- curl http://httpbin.default:8000/ip -s -o /dev/null -w "%{http_code}\n" 403
這是因為在授權策略中拒絕除使用
old-td/ns/sleep-allow/sa/sleep
身份標識的所有請求,即sleep-allow
命名空間中的sleep
應用使用的老標識。當遷移到一個新的信任域new-td
之后,該sleep應用的標識變為了new-td/ns/sleep-allow/sa/sleep
,與授權策略不匹配。因此sleep-allow
命名空間中的sleep
到httpbin
就被拒絕了。在istio 1.4之前需要手動修改授權策略來使之正常工作,現在有了更加方便的方式。遷移信任域,不使用信任域別名
使用信任域別名遷移信任域
-
使用新的信任域和信任域別名安裝istio
# cat cni-annotations.yaml apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: components: cni: enabled: true namespace: kube-system values: meshConfig: trustDomain: new-td trustDomainAliases: - old-td certificates: - secretName: dns.example1-service-account dnsNames: [example1.istio-system.svc, example1.istio-system] - secretName: dns.example2-service-account dnsNames: [example2.istio-system.svc, example2.istio-system] cni: excludeNamespaces: - istio-system - kube-system chained: false cniBinDir: /var/lib/cni/bin cniConfDir: /etc/cni/multus/net.d cniConfFileName: istio-cni.conf sidecarInjectorWebhook: injectedAnnotations: "k8s.v1.cni.cncf.io/networks": istio-cni
-
不修改任何授權策略,校驗到
httpbin
的請求default命名空間中的sleep到httpbin的請求被拒絕
# kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl http://httpbin.default:8000/ip -s -o /dev/null -w "%{http_code}\n" 403
sleep-allow命名空間中的sleep到httpbin的請求被允許
# kubectl exec "$(kubectl -n sleep-allow get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -n sleep-allow -- curl http://httpbin.default:8000/ip -s -o /dev/null -w "%{http_code}\n" 200
最佳實踐
從istio 1.4開始,當編寫授權策略時,需要使用cluster.local
作為策略的信任域,例如cluster.local/ns/sleep-allow/sa/sleep
。注意,上面情況下,cluster.local
並不是istio網格的信任域(信任域為old-td
)。但是在授權策略中,cluster.local
是一個指向當前信任域的指針,即old-td
(或后面的 new-td
)。通過在授權策略中使用cluster.local
,當遷移到一個新的信任域時,istio會探測並將新的信任域與就的信任域一視同仁,而無需使用別名。
按照上面的說法,將創建的授權策略的principals字段修改為
cluster.local/ns/sleep-allow/sa/sleep
,重新測試連通性,可以得到與使用別名相同的結果。
卸載
$ kubectl delete authorizationpolicy service-httpbin.default.svc.cluster.local
$ kubectl delete deploy httpbin; kubectl delete service httpbin; kubectl delete serviceaccount httpbin
$ kubectl delete deploy sleep; kubectl delete service sleep; kubectl delete serviceaccount sleep
$ kubectl delete namespace sleep-allow
$ istioctl manifest generate --set profile=demo -f td-installation.yaml | kubectl delete --ignore-not-found=true -f -
$ rm ./td-installation.yaml