Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug: JWT Plugin checks for expiration claim a priori #11262

Closed
mikyll opened this issue May 16, 2024 · 1 comment
Closed

bug: JWT Plugin checks for expiration claim a priori #11262

mikyll opened this issue May 16, 2024 · 1 comment

Comments

@mikyll
Copy link

mikyll commented May 16, 2024

Current Behavior

Note: I'm using Apisix Ingress Controller on Minikube, with Apisix CRDs.

Case 1 (OK)

  • ApisixConsumer doesn't specify the exp configuration parameter;
  • the JWT token includes a exp claim, but it's expired;

Result: the JWT validation fails due to exp claim, since the token is expired.

Case 2 (?)

  • ApisixConsumer doesn't specify the exp configuration parameter;
  • the JWT token does not include a exp claim;

Result: the JWT validation fails due to missing expor nbf claims.

Case 3 (OK)

  • ApisixConsumer specifies the exp configuration parameter;
  • the JWT token does not include a exp claim;

Result: the JWT validation fails due to missing expor nbf claims.

Expected Behavior

Do not check the exp and nbf claims by default, since the standard specification says they should be optional.

Instead, verify these claims only if they are specifyied in ApisixConsumer fields, or maybe provide configuration parameters to enable/disable the check (Kong does something similar).

References

4.1.4. "exp" (Expiration Time) Claim

The "exp" (expiration time) claim identifies the expiration time on
or after which the JWT MUST NOT be accepted for processing. The
processing of the "exp" claim requires that the current date/time
MUST be before the expiration date/time listed in the "exp" claim.

Implementers MAY provide for some small leeway, usually no more than
a few minutes, to account for clock skew. Its value MUST be a number
containing a NumericDate value. Use of this claim is OPTIONAL.

4.1.5. "nbf" (Not Before) Claim

The "nbf" (not before) claim identifies the time before which the JWT
MUST NOT be accepted for processing. The processing of the "nbf"
claim requires that the current date/time MUST be after or equal to
the not-before date/time listed in the "nbf" claim. Implementers MAY
provide for some small leeway, usually no more than a few minutes, to
account for clock skew. Its value MUST be a number containing a
NumericDate value. Use of this claim is OPTIONAL.

Error Logs

No response

Steps to Reproduce

NB: I'm setting Service apisix-gateway as a LoadBalancer for simplicity. I'm also using some mock RS256 public/private keys.

  1. Start a minikube cluster and launch tunnel command, leaving the terminal running in background:
minikube start --driver=docker
minikube tunnel
  1. Install Apisix Ingress Controller:
# Update charts
helm repo add apisix https://charts.apiseven.com
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Create namespace
kubectl create ns ingress-apisix

# Install Apisix
helm install apisix apisix/apisix \
  --set service.type=LoadBalancer \
  --namespace ingress-apisix \
  --set ingress-controller.enabled=true \
  --set ingress-controller.config.apisix.serviceNamespace=ingress-apisix

Once the installation is completed, minikube tunnel should pick up the apisix-gateway service and we will be able to send a curl request to the proxy on localhost:

curl -s -i "localhost:80/"
HTTP/1.1 404 Not Found
Date: Wed, 15 May 2024 14:07:06 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.9.1

{"error_msg":"404 Route Not Found"}
  1. Declare Kubernetes resources used for testing:
Show/Hide
# File 'resources.yaml'

apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      run: httpbin
  template:
    metadata:
      labels:
        run: httpbin
    spec:
      containers:
      - name: httpbin
        image: kennethreitz/httpbin
        ports:
        - containerPort: 80
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: httpbin
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
  name: httpbin-test-auth-jwt
spec:
  authParameter:
    jwtAuth:
      value:
        key: "my-key"
        algorithm: "RS256"
        base64_secret: false
        public_key: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
          4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
          +qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
          kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
          0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
          cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
          mwIDAQAB
          -----END PUBLIC KEY-----
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: httpbin-test-auth-jwt
spec:
  http:
  - name: rule1
    match:
      paths:
      - /*
    backends:
    - serviceName: httpbin
      servicePort: 80
    authentication:
      enable: true
      type: jwtAuth
  1. Create them inside the cluster:
kubectl apply -f resources.yaml
  1. Test the route
curl -s -i -X GET "localhost/get?foo1=bar1&foo2=bar2"
HTTP/1.1 401 Unauthorized
Date: Thu, 16 May 2024 10:05:55 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.9.1

{"message":"Missing JWT token in request"}
  1. Use the following token:
    • header:
    {
      "alg": "RS256",
      "typ": "JWT"
    }
    • payload without expiration claim:
    {
      "key": "my-key",
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
  2. Save the token in a variable:
JWT_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJteS1rZXkiLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.kQ-h3S0wgwDWIaOV_JNOlpck8JXAxAAtIto3hzHee4PHisx8xZcajSaX6PXf5cQW-H8uIOf7UUJCr3_62LV2hy4sSLwXcpzK5QIYRu4GO6g4q9Lv3yhYw0h2rP0bcFmkoVM91S7T0BEFA9m3H8zyKpF6qy9GscC7xoNL49W7TVhoetm4GeiKpngLCvUbGEjLWUrzx1o9JRruOe6ydv4VhOi8angH4By7kcs-XKLepVr1HyzQacw8Lyoyu4CumviefpWkwzDfDINf0r3Ad-WqAsoOGBhJh4fybe6xpIO_uWOJ95JWHUaQE3HWM_Q9fUuZvyR1p7oizbmWUxipaYIaZA"
  1. Open apisix gateway logs in another terminal, to detect the "expiration check" message:
kubectl logs -n "ingress-apisix" -f deployments/$(kubectl get deployment -n "ingress-apisix" --no-headers | cut -f1 -d' '| grep 'apisix$')
  1. Send a HTTP request with the token:
curl -s -i -X GET "localhost/get?foo1=bar1&foo2=bar2" -H "Authorization: Bearer ${JWT_TOKEN}"                                                                 HTTP/1.1 401 Unauthorized
Date: Thu, 16 May 2024 10:14:35 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.9.1

{"message":"failed to verify jwt"}
  1. We can see the warning message in apisix logs:
2024/05/16 10:19:20 [warn] 72#72: *1053197 [lua] jwt-auth.lua:381: phase_func(): failed to verify jwt: Missing one of claims - [ nbf, exp ]., client: 10.244.0.1, server: _, request: "GET /get?foo1=bar1&foo2=bar2 HTTP/1.1", host: "localhost"
2024/05/16 10:19:20 [warn] 72#72: *1053197 [lua] plugin.lua:1160: run_plugin(): jwt-auth exits with http status code 401, client: 10.244.0.1, server: _, request: "GET /get?foo1=bar1&foo2=bar2 HTTP/1.1", host: "localhost"
10.244.0.1 - - [16/May/2024:10:19:20 +0000] localhost "GET /get?foo1=bar1&foo2=bar2 HTTP/1.1" 401 46 0.000 "-" "curl/8.2.1" - - - "http://localhost"

Environment

  • APISIX version (run apisix version):
/usr/local/openresty//luajit/bin/luajit ./apisix/cli/apisix.lua version
3.9.1
  • Operating system (run uname -a):
Linux apisix-69f8d49c7b-vdcvf 5.15.133.1-microsoft-standard-WSL2 #1 SMP Thu Oct
5 21:02:42 UTC 2023 x86_64 GNU/Linux
  • OpenResty / Nginx version (run openresty -V or nginx -V):
Show/Hide
nginx version: openresty/1.25.3.1
built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
built with OpenSSL 3.2.0 23 Nov 2023
TLS SNI support enabled
configure arguments: --prefix=/usr/local/openresty/nginx --with-cc-opt='-O2 -DAPISIX_RUNTIME_VER=1.2.0 -DNGX_GRPC_CLI_ENGI
NE_PATH=/usr/local/openresty/libgrpc_engine.so -DNGX_HTTP_GRPC_CLI_ENGINE_PATH=/usr/local/openresty/libgrpc_engine.so -DNG
X_LUA_ABORT_AT_PANIC -I/usr/local/openresty/zlib/include -I/usr/local/openresty/pcre/include -I/usr/local/openresty/openss
l3/include' --add-module=../ngx_devel_kit-0.3.3 --add-module=../echo-nginx-module-0.63 --add-module=../xss-nginx-module-0.
06 --add-module=../ngx_coolkit-0.2 --add-module=../set-misc-nginx-module-0.33 --add-module=../form-input-nginx-module-0.12
 --add-module=../encrypted-session-nginx-module-0.09 --add-module=../srcache-nginx-module-0.33 --add-module=../ngx_lua-0.1
0.26 --add-module=../ngx_lua_upstream-0.07 --add-module=../headers-more-nginx-module-0.37 --add-module=../array-var-nginx-
module-0.06 --add-module=../memc-nginx-module-0.20 --add-module=../redis2-nginx-module-0.15 --add-module=../redis-nginx-mo
dule-0.3.9 --add-module=../ngx_stream_lua-0.0.14 --with-ld-opt='-Wl,-rpath,/usr/local/openresty/luajit/lib -Wl,-rpath,/usr
/local/openresty/wasmtime-c-api/lib -L/usr/local/openresty/zlib/lib -L/usr/local/openresty/pcre/lib -L/usr/local/openresty
/openssl3/lib -Wl,-rpath,/usr/local/openresty/zlib/lib:/usr/local/openresty/pcre/lib:/usr/local/openresty/openssl3/lib' --
add-module=/tmp/tmp.EQbG5FbABp/openresty-1.25.3.1/../mod_dubbo-1.0.2 --add-module=/tmp/tmp.EQbG5FbABp/openresty-1.25.3.1/.
./ngx_multi_upstream_module-1.2.0 --add-module=/tmp/tmp.EQbG5FbABp/openresty-1.25.3.1/../apisix-nginx-module-1.16.0 --add-
module=/tmp/tmp.EQbG5FbABp/openresty-1.25.3.1/../apisix-nginx-module-1.16.0/src/stream --add-module=/tmp/tmp.EQbG5FbABp/op
enresty-1.25.3.1/../apisix-nginx-module-1.16.0/src/meta --add-module=/tmp/tmp.EQbG5FbABp/openresty-1.25.3.1/../wasm-nginx-
module-0.7.0 --add-module=/tmp/tmp.EQbG5FbABp/openresty-1.25.3.1/../lua-var-nginx-module-v0.5.3 --add-module=/tmp/tmp.EQbG
5FbABp/openresty-1.25.3.1/../grpc-client-nginx-module-v0.5.0 --add-module=/tmp/tmp.EQbG5FbABp/openresty-1.25.3.1/../lua-re
sty-events-0.2.0 --with-poll_module --with-pcre-jit --with-stream --with-stream_ssl_module --with-stream_ssl_preread_modul
e --with-http_v2_module --with-http_v3_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_mo
dule --with-http_stub_status_module --with-http_realip_module --with-http_addition_module --with-http_auth_request_module
--with-http_secure_link_module --with-http_random_index_module --with-http_gzip_static_module --with-http_sub_module --wit
h-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-threads --with-compat --w
ith-stream --without-pcre2 --with-http_ssl_module

Questions

  • Is there any specific reason why it's implemented this way, instead of leaving the choice to the user (similarly to how Kong JWT plugin does), or am I missing something? 👀
  • What's the purpose of exp field in ApisixConsumer?
@hanqingwu
Copy link
Contributor

Yes, from source code and your testcase, I think exp is a require value not a optional value , default value is 86400

@mikyll mikyll closed this as completed May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

2 participants