Istio安全-授權(實操三)

charlieroro發表於2020-09-01

Istio安全-授權

授權HTTP流量

本節展示如何在istio網格中授權HTTP流量。

部署Bookinfo。由於下例在策略中使用了principal和namespace,因此需要啟用mutual TLS。

為使用HTTP流量的負載配置訪問控制

本任務展示瞭如何使用istio的授權設定訪問控制。首先,使用簡單的deny-all策略拒絕所有到負載的請求,然後增量地授權到負載的訪問。

下面使用kubernetes的service account授權istio網格中的HTTP訪問

  1. 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沒有其他規則允許流量訪問網格中的負載。

  2. 執行如下命令建立一個 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負載授權訪問detailsreviews負載。下面,需要配置一個策略來授權訪問這些負載。

  3. 執行如下命令建立details-viewer策略來允許productpage負載使用 cluster.local/ns/default/sa/bookinfo-productpage service account發起GET請求訪問details

    productpage的deployment中可以看到它使用了service account bookinfo-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
    
  4. 執行如下命令建立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負載進行訪問。

  5. 執行下面命令建立策略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中部署兩個名為sleeptcp-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負載的訪問控制

  1. 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
    
  2. 使用如下命令校驗允許到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
    
  3. 使用如下命令校驗允許到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
    
  4. 校驗到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
    
  5. 使用如下命令將策略更新為僅在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
    
  6. 校驗到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
    
  7. 校驗到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
    
  8. 使用如下命令將策略更新為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
    
  9. 校驗到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
    
  10. 校驗到埠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名稱空間中部署部署兩個負載:httpbinsleep。兩個負載都執行了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允許請求

  1. 使用如下命令在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進行校驗

  2. 校驗帶無效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
    
  3. 校驗不帶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
    
  4. 下面命令會給foo名稱空間中的httpbin負載建立require-jwt授權策略。該策略會要求所有的請求都必須包含一個有效的JWT(requestPrincipaltesting@secure.istio.io/testing@secure.istio.io)。Istio通過將JWT token的isssub與一個/分隔符組合來構造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
    
  5. 獲取key為isssub,且值(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"}
    
  6. 校驗帶有效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
    
  7. 校驗帶無效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
    
  8. 如下命令會更新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
    
  9. 獲取將 groups claim設定為字串列表的JWT: group1group2:

    # 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"}
    
  10. 校驗在請求的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
    
  11. 校驗請求的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.0OIDC 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 全部屬性

參考

  1. 基於OIDC實現istio來源身份驗證
  2. JWT 授權
  3. JWT Claims

使用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

明確拒絕一個請求

  1. 下面命令會為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
    
  2. 校驗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
    
  3. 校驗允許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
    
  4. 更新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
    
  5. 校驗當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
    
  6. 校驗當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
    
  7. 下面命令會建立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
    
  8. 校驗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
    
  9. 校驗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
    
  10. 校驗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_HOSTINGRESS_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列表

  1. 下面命令會為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
    
  2. 校驗到ingress閘道器的請求被拒絕了

    # curl "$INGRESS_HOST":"$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
    403
    
  3. 更新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
    
  4. 校驗到ingress閘道器的請求變為允許

    # curl "$INGRESS_HOST":"$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
    200
    
  5. 更新 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
    
  6. 校驗到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(除非在安裝時指定了其他域)。

部署

  1. 使用使用者信任域安裝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
    
  2. default名稱空間中部署httpbin,並在defaultsleep-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
    
  3. 配置如下授權策略,拒絕除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中的sleephttpbin的請求,該請求被拒絕

# 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中的sleephttpbin的請求,該請求被允許

# 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

不使用信任域別名遷移信任域

  1. 使用新的信任域安裝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
    
  2. 重新部署httpbin和sleep,使其接收來自新的istio控制面的變更

    $ kubectl delete pod --all
    $ kubectl delete pod --all -n sleep-allow
    
  3. 校驗defaultsleep-allow名稱空間的sleephttpbin的請求都被拒絕了

    # 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名稱空間中的sleephttpbin就被拒絕了。在istio 1.4之前需要手動修改授權策略來使之正常工作,現在有了更加方便的方式。

    遷移信任域,不使用信任域別名

使用信任域別名遷移信任域

  1. 使用新的信任域和信任域別名安裝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
    
  2. 不修改任何授權策略,校驗到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

相關文章