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 on 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

  • Make sure that the Istio version is 1.6 or later. Otherwise, you cannot use the RequestAuthentication feature.
  • 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.

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 a sample service

  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 of the ASM instance.
  4. On the details page of the ASM instance, click Namespaces in the left-side navigation pane. On the Namespaces page, click Create.
  5. In the Create Namespace panel, set the Name and Tag parameters. In this example, set the Name parameter to foo and the Tag parameter to istio-injection:enabled. The settings are used to create a namespace named foo with automatic sidecar injection enabled.
  6. Click OK to create the foo namespace.
  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 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
    Execution result:

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

    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 Security > RequestAuthentication in the left-side navigation pane. On the RequestAuthentication page, click Create.
  2. In the Create panel, select foo from the Namespaces drop-down list. In the code editor, edit a YAML file in the foo namespace to create 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 parameter in the preceding YAML code specifies a rule for requests of the httpbin service. If the request header contains an access token, the decoded value of the iss parameter must be testing@secure.istio.io. The jwks parameter specifies how to generate the access token. For more information, see JSON Web Key (JWK).

  3. Click OK to create the request authentication policy.
    Execution result:

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

    • Use the following code to determine when to return 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 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 Security > AuthorizationPolicy in the left-side navigation pane. On the AuthorizationPolicy page, click Create.
  2. In the Create panel, select foo from the Namespaces drop-down list. In the code editor, edit a YAML file in the foo namespace to create an 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 httpbin, the system checks the decoded access token in the request header. The request is allowed only when the values of the iss and sub parameters in the decoded access token are the same as those indicated by the source.requestPrincipals parameter. The source.requestPrincipals parameter is in the format of iss/sub. In this example, the value of the source.requestPrincipals parameter 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 response 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 OK to create the JWT authorization policy.
    Execution result:

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

    • Use the following code to determine when to return 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 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 Security > AuthorizationPolicy in the left-side navigation pane.
  2. On the AuthorizationPolicy page, find the authorization policy that you want to modify and click YAML in the Actions column.
  3. In the Edit dialog box, 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 httpbin, 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 when the decoded access token meets the following conditions: The value of the iss parameter is testing@secure.istio.io, the value of the sub parameter is testing@secure.istio.io, and the groups parameter 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 response 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 to update the JWT authorization policy.
    Execution result:

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

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