istio从入门到放弃系列(三)——istio安全配置实战

530 阅读5分钟

istio安全配置实战

上次我们进行了istio的流量控制的一些基本实验,这次我们继续通过官方给的DEMO和我自己设计的一些场景来进行安全配置的试验,主要是为了解决日常集群使用中,对于安全路由的设置。 本次有以下几个场景:

  • JWT权限认证
  • HTTP流量授权
  • TCP流量授权

环境配置

如果在istio从入门到放弃系列(二)——istio流量控制实战中进行过配置可以略过这一步。
下载官方DEMO

kubectl label namespace default istio-injection=enabled #开启sidecar自动注入
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml #部署初始应用
kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml #部署默认规则

JWT权限认证

环境准备

JWT是istio提供的一种HTTP请求认证方案,我们需要在yaml中放入JWK,想生成JWK我们先来生成一个RSA的密钥

#  生成 2048 位(不是 256 位)的 RSA 密钥
openssl genrsa -out rsa-private-key.pem 2048

然后用我们的密钥来生成对应的JWT和公钥

# 需要先安装依赖: pip3 install jwcrypto -i https://pypi.tuna.tsinghua.edu.cn/simple
from jwcrypto.jwk import JWK
from pathlib import Path

private_key = Path("rsa-private-key.pem").read_bytes()
jwk = JWK.from_pem(private_key)

# 导出公钥 RSA Public Key
public_key = jwk.public().export_to_pem()
with open('./public.pem','w') as f:    #设置文件对象
    f.write(bytes.decode(public_key))

print("="*30)

# 导出 JWK
jwk_bytes = jwk.public().export()
with open('./jwt.json','w') as f:    #设置文件对象
    f.write(jwk_bytes)

粘上我测试的的私钥密钥和JWK

jwt.json

{"e":"AQAB","kid":"Rv59tGv8jD63K0FcPKVWWOX8_y1LLlmp3mUSG_xrzfg","kty":"RSA","n":"pzQoa-R1yP2fz6kyWfeIZfCeYkVmXqK7hI1PuF8LSUzcITZoLYcp54hGm6Qdi059RFlPTScWaPLfGOxB75RRZt7uw2FoMgH6X2Zi3KdQdytmdz3viU2G-kgdn1pRoq9wVXGomYL0myzOt7Uz8cLHvCG-pAGNQYbdeQ_NeMgHmCgLSmcXjRQQfg55SPbB2t4rZM_IJs0jL-0ckEXvN1UwYFVifJ6eGquyV_bdoknkdCFH3eTpgCSXMwJGkh0JXSBQLW5hHPa2xOA90JYfTNuqfSDMBmRy2vVurbgZeJggFgSD-atP8_JXZ1oz4ZwJwlFjX0F1lkpTvomDORL7ATrtHQ"}

public.pem

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApzQoa+R1yP2fz6kyWfeI
ZfCeYkVmXqK7hI1PuF8LSUzcITZoLYcp54hGm6Qdi059RFlPTScWaPLfGOxB75RR
Zt7uw2FoMgH6X2Zi3KdQdytmdz3viU2G+kgdn1pRoq9wVXGomYL0myzOt7Uz8cLH
vCG+pAGNQYbdeQ/NeMgHmCgLSmcXjRQQfg55SPbB2t4rZM/IJs0jL+0ckEXvN1Uw
YFVifJ6eGquyV/bdoknkdCFH3eTpgCSXMwJGkh0JXSBQLW5hHPa2xOA90JYfTNuq
fSDMBmRy2vVurbgZeJggFgSD+atP8/JXZ1oz4ZwJwlFjX0F1lkpTvomDORL7ATrt
HQIDAQAB
-----END PUBLIC KEY-----

rsa-private-key.pem

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEApzQoa+R1yP2fz6kyWfeIZfCeYkVmXqK7hI1PuF8LSUzcITZo
LYcp54hGm6Qdi059RFlPTScWaPLfGOxB75RRZt7uw2FoMgH6X2Zi3KdQdytmdz3v
iU2G+kgdn1pRoq9wVXGomYL0myzOt7Uz8cLHvCG+pAGNQYbdeQ/NeMgHmCgLSmcX
jRQQfg55SPbB2t4rZM/IJs0jL+0ckEXvN1UwYFVifJ6eGquyV/bdoknkdCFH3eTp
gCSXMwJGkh0JXSBQLW5hHPa2xOA90JYfTNuqfSDMBmRy2vVurbgZeJggFgSD+atP
8/JXZ1oz4ZwJwlFjX0F1lkpTvomDORL7ATrtHQIDAQABAoIBADXiwFgtM6yH2HYz
YC/Qb5vy1Qh0t04ugeJxLE0ODHQeGh92ClMJ6X5d0+ubE45uyD47ziveOgGurCm7
EnDkyustU6OSA+OB8a/HLntQTMVrLkWlp0oHu0Vz8mAF2qNkiP5wd9apdq1/3ksk
Uc5LaNV/xpHSkjSZA01dw2l2hcQKNkkKthcO0g836l/3Sw1YlP64YZkBn19m+Xim
YDshEjoA4/vlyf8xTlgreKa/t9YhRIpM/l6h774cahF8PRQeBQELIx4UKTzqMJf2
0DxyjHLMv0Yt4HI5aXwSKEg7UR7ke3Tul4cwi/lyGfVTniQHptHMcAOXwrYhki5I
pwp1DwECgYEA161tUcTsyQrZQohnRLP3srqyBkPCo9zCy74ezsDwK4AZZ8/797Gm
pETiqLv9FC3mYPk1DS5P3MmCiW326FyYsQ66WmYmoSXPjHzVi8mLZxegscjm3/47
jPwJgusAK6EqNatmLkOlZml8yWrHMC8sQtAoTo1hM+wrYk3QUglMykECgYEAxna5
FUiuiClhoPqZH+NVCBvNotdaomnvjm9GRz4N4SSzzdGFba+VGi1iCRGXKqXM0G1H
symZTung/h8EqDD8pRUgT1/twH9HmuoLETaB9YqZa+8nK5Gj9XsxIp1OiTLJuHo+
PdLFTze0I+SEKDrZRBRAobUZYo8t+DIFz3yNk90CgYEAvG6O9kPgxH0v+AsIfmPl
40dtxj9pTJTRtAQ1ElpK+xZ+G88AyxVxDFAK33Tu1bSMdOkFyrBNog6Ed+GVOMm9
teOyOMzKrzxDqvBd+jVqD/X6tZla7RRHnxOMk88RZQz3vdA0A/OiDGnZVnht8tEk
EHOg45Bt/lk2RjrJ6QKrDoECgYB6YnAUHfPy54Ha4W5X6bpf+7U9fAvaJ/WgIiJ2
gF/SvO1cOJ5NW39Y2y+fZAeSNxgsV5dldnuh3DvwuXQHu92wd2yrRf65PEQN1dHp
VXGi10tw8dN33KH9GXDdZaAunvEiH9AOE9G03ibqE1sj69ZbUxngHmt/CchRS5el
saskyQKBgQChCANNAmCKMEFWvv3CBr3rk8T5jV38N2nraq9m1/rwdmtkHJFpBAF9
9N4azkSMVTAd/Rpsdu3phH55Cuv4K/dyY9Lkus4AcPXFgBMEDP6ySZNdBPZHkt29
4M8mNjSJuQ3I0dWzVAkSC6XoTJiWZou3YJ9Qqxa9CxsQJQBTwd1uYA==
-----END RSA PRIVATE KEY-----

准备好JWK后我们部署两个工作负载

kubectl create ns foo
kubectl label namespace foo istio-injection=enabled
kubectl apply -f  samples/httpbin/httpbin.yaml -n foo
kubectl apply -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"


没问题,我们开始部署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: "ryan@lemonbox.com"
    jwks: |
      {
          "keys": [
            {
                "e":"AQAB",
                "kid":"Rv59tGv8jD63K0FcPKVWWOX8_y1LLlmp3mUSG_xrzfg",
                "kty":"RSA",
                "n":"pzQoa-R1yP2fz6kyWfeIZfCeYkVmXqK7hI1PuF8LSUzcITZoLYcp54hGm6Qdi059RFlPTScWaPLfGOxB75RRZt7uw2FoMgH6X2Zi3KdQdytmdz3viU2G-kgdn1pRoq9wVXGomYL0myzOt7Uz8cLHvCG-pAGNQYbdeQ_NeMgHmCgLSmcXjRQQfg55SPbB2t4rZM_IJs0jL-0ckEXvN1UwYFVifJ6eGquyV_bdoknkdCFH3eTpgCSXMwJGkh0JXSBQLW5hHPa2xOA90JYfTNuqfSDMBmRy2vVurbgZeJggFgSD-atP8_JXZ1oz4ZwJwlFjX0F1lkpTvomDORL7ATrtHQ"
            }
          ]
      }
EOF

jwks是我们的明文配置,可以切换成jwksUri引入json文件,issuer是配置的用户标示,在JWT配置中需要加上。
然后我们打开jwt通过这个网站来生成对应的TOKEN
注意我标注红框的位置,需要做对应的配置,exp一定要配置,不然会当成过期token。
尝试用错误TOKEN请求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/headers" -s -o /dev/null -H "Authorization: Bearer invalidToken" -w "%{http_code}\n"

可以看到,报了401的错,我们换成刚才生成的正确token试一下

export TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyeWFuQGxlbW9uYm94LmNvbSIsImlzcyI6InJ5YW5AbGVtb25ib3guY29tIiwiaWF0IjoxNTU2MjM5MDIyLCJleHAiOjQ2ODU5ODk3MDB9.KRhZHEfL-IPGySxG3vdf_WDXdcZADni0sDg_CIZ8qbrR1q44NPNC7pB45KjGsnOMyWxUrUg1kpPTk2_gPNUs5KEUrqq9AgpYBl9lC2RhEGUIRA2jsngiYBH_fWCDw3YFfD2MtNbV6R6Ijzg4ttxxnJbYM2qIt-VNGnTYFrrkCzJeEaLkKTdo9l1u-mKyI68VpV7K003d5E2e8r_JgbXb-S6vNg7koKRkXZXWWzsAXgdSB5hJPFj0cM9tCitGdIF3QX0xx9QV9UH35LP8L4WZr3uJjqIEGMLPn6ozdhHTivOWnvBOHEG8pGb47ntwIQF7U1OdnuDDshG82PcP45duAA
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"

请求成功,但是这时候JWT还不算完全配置OK,我们试一下不带TOKEN请求。

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"

发现请求也成功了,这是因为JWT只限制token的正确与否,并不会在乎是否有。

HTTP流量授权

为了解决这一问题,我们需要再加上一层AuthorizationPolicy的限制

kubectl delete -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: ["ryan@lemonbox.com/ryan@lemonbox.com"]
EOF

这里需要注意的是,form source这种配置在对从ingress访问的情况下并不生效,它是要求mTLS的,如果是配置到ingress级别不使用mTLS的话需要使用下面的yaml

kubectl delete -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: foo
spec:
  selector:
    matchLabels:
      app: httpbin
  action: ALLOW
  rules:
   - when:
      - key: request.auth.claims[iss]
        values: ["ryan@lemonbox.com"]
EOF

配置好后我们再尝试无TOKEN访问一下

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"

可以看到,返回了403错误,尝试使用正确的TOKEN

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"

我们也可以尝试在之前配置的网站上试一下

kubectl apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "jwt-reviews"
  namespace: default
spec:
  selector:
    matchLabels:
      app: productpage
  jwtRules:
  - issuer: "ryan@lemonbox.com"
    jwks: |
      {
          "keys": [
            {
                "e":"AQAB",
                "kid":"Rv59tGv8jD63K0FcPKVWWOX8_y1LLlmp3mUSG_xrzfg",
                "kty":"RSA",
                "n":"pzQoa-R1yP2fz6kyWfeIZfCeYkVmXqK7hI1PuF8LSUzcITZoLYcp54hGm6Qdi059RFlPTScWaPLfGOxB75RRZt7uw2FoMgH6X2Zi3KdQdytmdz3viU2G-kgdn1pRoq9wVXGomYL0myzOt7Uz8cLHvCG-pAGNQYbdeQ_NeMgHmCgLSmcXjRQQfg55SPbB2t4rZM_IJs0jL-0ckEXvN1UwYFVifJ6eGquyV_bdoknkdCFH3eTpgCSXMwJGkh0JXSBQLW5hHPa2xOA90JYfTNuqfSDMBmRy2vVurbgZeJggFgSD-atP8_JXZ1oz4ZwJwlFjX0F1lkpTvomDORL7ATrtHQ"
            }
          ]
      }
EOF

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: "reviews-viewer"
  namespace: default
spec:
  selector:
    matchLabels:
      app: productpage
  action: ALLOW
  rules:
  - from:
    - source:
       requestPrincipals: ["ryan@lemonbox.com/ryan@lemonbox.com"]
EOF

使用chrome的拓展程序bwisse可以在访问时配置访问头

不开启时:

开启后:

权限配置生效了。

TCP流量授权

环境配置

TCP授权配置与HTTP并没有太大的区别,先部署bookinfo-ratings-v2的v2版本

kubectl apply -f samples/bookinfo/platform/kube/bookinfo-ratings-v2.yaml
kubectl apply -f samples/bookinfo/networking/destination-rule-all-mtls.yaml #新的目标规则
kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-db.yaml #更新 reviews 工作负载以只使用 v2 版本的 ratings 工作负载
kubectl apply -f samples/bookinfo/platform/kube/bookinfo-db.yaml #部署 MongoDB 工作负载


现在去浏览发现已经能显示出数据库内的书评了。先尝试一下禁用mongo所有的TCP

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
spec:
  selector:
    matchLabels:
      app: mongodb
EOF


发现页面右下角的 Book Reviews 显示了错误信息:“Ratings service is currently unavailable”,此时无法调起对应的mongo数据,对27017端口单独创建一个允许规则。

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: bookinfo-ratings-v2
spec:
  selector:
    matchLabels:
      app: mongodb
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/bookinfo-ratings-v2"]
    to:
    - operation:
        ports: ["27017"]
EOF

再次刷新页面已经成功部署了通过 TCP 流量进行通信的工作负载,并应用了网格级别和工作负载级别的授权策略来对请求实施访问控制。