在服务网格中配置JWT(JSON Web Token)请求鉴权,可以实现来源认证,又称为最终用户认证。在接收用户请求时,该配置用于认证请求头信息中的Access Token是否可信,并授权给来源合法的请求。本文介绍如何在ASM中对服务进行JWT请求鉴权。

前提条件

  • 请确保Istio版本≥1.6,否则将不支持RequestAuthentication功能。
  • 已创建至少一个ASM实例,并添加至少一个ACK集群到该实例中。详情请参见添加集群到ASM实例

背景信息

服务网格包含两种认证方式:
  • 传输认证:基于双向TLS技术,常用于服务间通信认证。
  • 来源认证:基于JWT技术,常用于客户端和服务之间的请求认证。

本文介绍的JWT请求授权正是为了实现来源认证,JWT是一种用于双方之间传递安全信息的表述性声明规范。JWT的相关资料请参见JWT官方文档

步骤一:部署示例服务

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏选择网格实例 > 全局命名空间,然后在右侧页面单击新建
  5. 新建命名空间面板中,填写名称标签。名称为foo,标签为istio-injection:enabled。表示创建命名空间为foo,并启用istio-injection
  6. 单击确定,完成foo的命名空间创建。
  7. 在本地执行以下命令,部署官方示例服务httpbinsleep
    kubectl \
      --kubeconfig "$USER_CONFIG" \
      -n foo \
      apply -f "$ISTIO_HOME"/samples/httpbin/httpbin.yaml
    
    kubectl \
      --kubeconfig "$USER_CONFIG" \
      -n foo \
      apply -f "$ISTIO_HOME"/samples/sleep/sleep.yaml
                            
  8. 执行如下命令,从而实现在Pod就绪前系统会一直等待。
    kubectl --kubeconfig "$USER_CONFIG" -n foo get po
    kubectl --kubeconfig "$USER_CONFIG" -n foo wait --for=condition=ready pod -l app=httpbin
    kubectl --kubeconfig "$USER_CONFIG" -n foo wait --for=condition=ready pod -l app=sleep
    执行结果:

    sleep容器内,编辑YAML,验证是否可以请求httpbin。验证结果http_code值为200即请求成功。

    sleep_pod=$(kubectl --kubeconfig "$USER_CONFIG" get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})
    RESULT=$(kubectl \
      --kubeconfig "$USER_CONFIG" \
      exec "$sleep_pod" -c sleep -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}")
    if [[ $RESULT != "200" ]]; then
      echo "http_code($RESULT) should be 200"
      exit
    fi

步骤二:创建请求认证

  1. 在网格详情页面左侧导航栏选择零信任安全 > 请求身份认证,然后在右侧页面单击使用YAML创建
  2. 设置命名空间foo,然后在文本框中输入请求身份认证的YAML内容。

    jwt-example.yaml示例如下:

    apiVersion: "security.istio.io/v1beta1"
    kind: "RequestAuthentication"
    metadata:
      name: "jwt-example"
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: httpbin
      jwtRules:
      - issuer: "testing@secure.istio.io"
        jwks: '{ "keys":[ {"e":"AQAB","kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ","kty":"RSA","n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"}]}'
    说明

    这段YAML实现的策略为:当请求httpbin服务时,需匹配jwtRules中定义的规则,即请求头中如果包含Access Token信息,解码后的iss的值必须为testing@secure.istio.iojwks中定义了Token生成的相关信息,详情请参见JWT官方文档

  3. 单击创建
    执行结果:

    请求头中包含合法的Access Token时返回状态码为200,否则返回状态码为401。

    • 返回状态码为200如下所示。
      for ((i = 1; i <= 10; i++)); do
        RESULT=$(kubectl \
          --kubeconfig "$USER_CONFIG" \
          exec "$sleep_pod" \
          -c sleep \
          -n foo \
          -- curl "http://httpbin.foo:8000/headers" \
          -s \
          -o /dev/null \
          -w "%{http_code}")
        if [[ $RESULT != "200" ]]; then
          echo "http_code($RESULT) should be 200"
          exit
        fi
      done
    • 返回状态码为401如下所示。
      for ((i = 1; i <= 5; i++)); do
          RESULT=$(kubectl \
            --kubeconfig "$USER_CONFIG" \
            exec "$sleep_pod" \
            -c sleep \
            -n foo \
            -- curl "http://httpbin.foo:8000/headers" \
            -s \
            -o /dev/null \
            -H "Authorization: Bearer invalidToken" \
            -w "%{http_code}")
          if [[ $RESULT != "401" ]]; then
            echo "http_code($RESULT) should be 401"
            exit
          fi
      done

步骤三:创建JWT授权策略

  1. 在网格详情页面左侧导航栏选择零信任安全 > 授权策略,然后在右侧页面单击使用YAML创建
  2. 设置命名空间foo,然后在文本框中输入授权策略的YAML内容。

    require-jwt.yaml示例如下:

    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"]
    说明

    这段YAML实现的策略为:请求httpbin服务时,只有请求头Token解码后,符合iss的值+/+sub的值(即source.requestPrincipals)为testing@secure.istio.io/testing@secure.istio.io,请求权限才为ALLOW

    • Token信息如下所示。
      TOKEN='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg'
    • 解析Token如下所示。
      echo $TOKEN | cut -d '.' -f2 - | base64 --decode -
    • 解析Token的输出信息如下所示。
      {"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"testing@secure.istio.io","sub":"testing@secure.istio.io"}

    JWT官网也提供了同样的解析Token能力,图形化输出如下图所示。

    JWT
  3. 单击创建
    执行结果:

    请求头中包含合法的Access Token时返回状态码为200,否则返回状态码为403。

    • 返回状态码为200如下所示。
      for ((i = 1; i <= 10; i++)); do
          RESULT=$(kubectl \
            --kubeconfig "$USER_CONFIG" \
            exec "$sleep_pod" \
            -c sleep \
            -n foo \
            -- curl "http://httpbin.foo:8000/headers" \
            -s \
            -o /dev/null \
            -H "Authorization: Bearer $TOKEN" \
            -w "%{http_code}")
          if [[ $RESULT != "200" ]]; then
            echo "http_code($RESULT) should be 200"
            exit
          fi
      done
    • 返回状态码为403如下所示。
      for ((i = 1; i <= 10; i++)); do
          RESULT=$(kubectl \
            --kubeconfig "$USER_CONFIG" \
            exec "$sleep_pod" \
            -c sleep \
            -n foo \
            -- curl "http://httpbin.foo:8000/headers" \
            -s \
            -o /dev/null \
            -w "%{http_code}")
          if [[ $RESULT != "403" ]]; then
            echo "http_code($RESULT) should be 403"
            exit
          fi
      done

步骤四:追加JWT授权策略

  1. 在网格详情页面左侧导航栏选择零信任安全 > 授权策略
  2. 授权策略页面单击require-jwt操作列的YAML
  3. 编辑面板中追加补充以下内容。

    require-jwt-group.yaml补充的内容。

        when:
        - key: request.auth.claims[groups]
          values: ["group1"]

    以下为完整的require-jwt-group.yaml示例。

    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"]
    说明

    这段YAML实现的策略为:请求httpbin服务时,只有请求头Token解码后,符合groups的值包含group1,请求权限才为ALLOW

    • Token信息如下所示。
      TOKEN_GROUP='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg'
    • 解析Token如下所示。
      echo "$TOKEN_GROUP" | cut -d '.' -f2 - | base64 --decode - | jq
    • 解析Token的输出信息如下所示。
      {
        "exp": 3537391104,
        "groups": [
          "group1",
          "group2"
        ],
        "iat": 1537391104,
        "iss": "testing@secure.istio.io",
        "scope": [
          "scope1",
          "scope2"
        ],
        "sub": "testing@secure.istio.io"
      }
  4. 单击确定
    执行结果:

    请求头中包含合法的Access Token时返回状态码为200,否则返回状态码为403。

    • 返回状态码为200如下所示。
      for ((i = 1; i <= 10; i++)); do
          RESULT=$(kubectl \
            --kubeconfig "$USER_CONFIG" \
            exec "$sleep_pod" \
            -c sleep \
            -n foo \
            -- curl "http://httpbin.foo:8000/headers" \
            -s \
            -o /dev/null \
            -H "Authorization: Bearer $TOKEN_GROUP" \
            -w "%{http_code}")
          if [[ $RESULT != "200" ]]; then
            echo "http_code($RESULT) should be 200"
            exit
          fi
      done
    • 返回状态码为403如下所示。
      for ((i = 1; i <= 10; i++)); do
          RESULT=$(kubectl \
            --kubeconfig "$USER_CONFIG" \
            exec "$sleep_pod" \
            -c sleep \
            -n foo \
            -- curl "http://httpbin.foo:8000/headers" \
            -s \
            -o /dev/null \
            -H "Authorization: Bearer $TOKEN" \
            -w "%{http_code}")
          if [[ $RESULT != "403" ]]; then
            echo "http_code($RESULT) should be 403"
            exit
          fi
      done