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