In Alibaba Cloud Service Mesh (ASM), you can configure JSON Web Token (JWT) authorization to authenticate the source of requests. This method is also called end-user authentication. When an application in an ASM instance configured with a JWT authorization policy receives a request, the system checks whether the request header contains a valid JWT. Only requests with valid JWTs are allowed. This topic describes how to configure a JWT authorization policy for an ASM instance.

Prerequisites

  • An ASM instance is created, and a Container Service for Kubernetes (ACK) cluster is added to the instance. For more information, see Add a cluster to an ASM instance.
  • The Istio version of your ASM instance is 1.6 or later. Otherwise, the RequestAuthentication feature is not supported.

Background information

ASM supports the following authentication methods:
  • Transmission authentication: This method is based on Mutual Transport Layer Security (mTLS) and used to authenticate communications among services.
  • Source authentication: This method is based on JWT and used to authenticate requests from the client to the server.

JWT is a standard that uses representative claims to secure information transmission between parties. It is used to authenticate the source of requests. For more information about JWT, see JWT official documentation.

Step 1: Deploy sample services

  1. Log on to the ASM console.
  2. In the left-side navigation pane, choose Service Mesh > Mesh Management.
  3. On the Mesh Management page, find the ASM instance that you want to configure. Click the name of the ASM instance or click Manage in the Actions column.
  4. On the details page of the ASM instance, choose ASM Instance > Global Namespace in the left-side navigation pane. On the Global Namespace page, click Create.
  5. In the Create Namespace panel, set the Name and Labels parameters. In this example, set the Name parameter to foo and add the istio-injection:enabled label. The settings are used to create a namespace named foo with automatic sidecar injection enabled.
  6. Click OK. The foo namespace is created.
  7. Run the following commands to deploy the official sample services httpbin and sleep:
    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. Run the following commands to keep the system waiting until the pods of the httpbin and sleep services get ready:
    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
    Result:

    In the pod of the sleep service, edit the YAML file of the ACK cluster to check whether requests can be sent to the httpbin service. If HTTP status code 200 is returned, requests can be sent to the httpbin service.

    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

Step 2: Create a request authentication policy

  1. On the details page of the ASM instance, choose Zero Trust Security > RequestAuthentication in the left-side navigation pane. On the RequestAuthentication page, click Create from YAML.
  2. On the Create page, select foo from the Namespace drop-down list, and enter the following content in the code editor to edit a YAML file for a request authentication policy.

    The following code provides an example of the content for the jwt-example.yaml file:

    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"}]}'
    Note

    The jwtRules field in the preceding YAML code specifies a rule for the requests of the httpbin service. If the request header contains an access token, the decoded value of the iss field must be testing@secure.istio.io. The jwks field specifies how to generate the access token. For more information, see JSON Web Key (JWK).

  3. Click Create.
    Result:

    If the request header contains a valid access token, HTTP status code 200 is returned. Otherwise, HTTP status code 401 is returned.

    • Use the following code to determine when to return HTTP status code 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
    • Use the following code to determine when to return HTTP status code 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

Step 3: Create a JWT authorization policy

  1. On the details page of the ASM instance, choose Zero Trust Security > AuthorizationPolicy in the left-side navigation pane. On the AuthorizationPolicy page, click Create from YAML.
  2. On the Create page, select foo from the Namespace drop-down list, and enter the following content in the code editor to edit a YAML file for a JWT authorization policy.

    The following code provides an example of the content for the require-jwt.yaml file:

    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"]
    Note

    The preceding authorization policy implements the following logic: When a request is sent to the httpbin service, the system checks the decoded access token in the request header. The request is allowed only if the values of the iss and sub fields in the decoded access token are the same as those specified by the source.requestPrincipals field. The source.requestPrincipals field is in the format of iss/sub. In this example, the value of the source.requestPrincipals field is testing@secure.istio.io/testing@secure.istio.io.

    • The following code provides an example of the access token:
      TOKEN='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg'
    • Run the following command to decode the access token:
      echo $TOKEN | cut -d '.' -f2 - | base64 --decode -
    • The following output shows the decoded value of the sample access token:
      null

    The JWT official website also provides a GUI for you to decode access tokens, as shown in the following figure.

    JWT
  3. Click Create.
    Result:

    If the request header contains a valid access token, HTTP status code 200 is returned. Otherwise, HTTP status code 403 is returned.

    • Use the following code to determine when to return HTTP status code 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
    • Use the following code to determine when to return HTTP status code 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

Step 4: Update the JWT authorization policy

  1. On the details page of the ASM instance, choose Zero Trust Security > AuthorizationPolicy in the left-side navigation pane.
  2. On the AuthorizationPolicy page, find the require-jwt policy and click YAML in the Actions column.
  3. In the Edit panel, add specified content in the code editor.

    The following code provides an example of the content to be added to the require-jwt-group.yaml file:

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

    The following code provides an example of the complete content for the require-jwt-group.yaml file:

    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"]
    Note

    When a request is sent to the httpbin service, the system checks the decoded access token in the request header. Then, the system applies the updated authorization policy to the request. The request is allowed only if the decoded access token meets the following conditions: The value of the iss field is testing@secure.istio.io, the value of the sub field is testing@secure.istio.io, and the groups field contains group1.

    • The following code provides an example of the access token:
      TOKEN_GROUP='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg'
    • Run the following command to decode the access token:
      echo "$TOKEN_GROUP" | cut -d '.' -f2 - | base64 --decode - | jq
    • The following output shows the decoded value of the sample access token:
      {
        "exp": 3537391104,
        "groups": [
          "group1",
          "group2"
        ],
        "iat": 1537391104,
        "iss": "testing@secure.istio.io",
        "scope": [
          "scope1",
          "scope2"
        ],
        "sub": "testing@secure.istio.io"
      }
  4. Click OK.
    Result:

    If the request header contains a valid access token, HTTP status code 200 is returned. Otherwise, HTTP status code 403 is returned.

    • Use the following code to determine when to return HTTP status code 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
    • Use the following code to determine when to return HTTP status code 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