Description
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 exp
or 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 exp
or 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.
- Start a minikube cluster and launch tunnel command, leaving the terminal running in background:
minikube start --driver=docker
minikube tunnel
- 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"}
- 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
- Create them inside the cluster:
kubectl apply -f resources.yaml
- 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"}
- Use the following token:
- header:
{ "alg": "RS256", "typ": "JWT" }
- payload without expiration claim:
{ "key": "my-key", "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
- Save the token in a variable:
JWT_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJteS1rZXkiLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.kQ-h3S0wgwDWIaOV_JNOlpck8JXAxAAtIto3hzHee4PHisx8xZcajSaX6PXf5cQW-H8uIOf7UUJCr3_62LV2hy4sSLwXcpzK5QIYRu4GO6g4q9Lv3yhYw0h2rP0bcFmkoVM91S7T0BEFA9m3H8zyKpF6qy9GscC7xoNL49W7TVhoetm4GeiKpngLCvUbGEjLWUrzx1o9JRruOe6ydv4VhOi8angH4By7kcs-XKLepVr1HyzQacw8Lyoyu4CumviefpWkwzDfDINf0r3Ad-WqAsoOGBhJh4fybe6xpIO_uWOJ95JWHUaQE3HWM_Q9fUuZvyR1p7oizbmWUxipaYIaZA"
- 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$')
- 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"}
- 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
ornginx -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?
Metadata
Metadata
Assignees
Labels
Type
Projects
Status