From 3780ed548ca033ee78d740443f9ba2baea0e2c4f Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Tue, 15 Nov 2022 13:52:53 -0800 Subject: [PATCH] Unpin JWT and use integer IAT values PyJWT 2.6.0 began performing validation of iat (issued at) claims in https://github.com/jpadilla/pyjwt/commit/9cb9401cc579f11dbb17181e8713f061f8e40ed4 I believe the intent of RFC7519 is to support any numeric values (including floating point) for iat, nbf, and exp, however, the PyJWT library has made the assumption that the values should be integers, and therefore when we supply an iat with decimal seconds, PyJWT will round down when validating the value. In our unit tests, this can cause validation errors. In order to avoid any issues, we will round down the times that we supply when generating JWT tokens and supply them as integers in accordance with the robustness principle. Change-Id: Ia8341b4d5de827e2df8878f11f2d1f52a1243cd4 --- doc/source/authentication.rst | 4 +- requirements.txt | 2 +- tests/unit/test_auth.py | 2 +- tests/unit/test_web.py | 78 ++++++++++++++-------------- tests/zuul_client/test_zuulclient.py | 10 ++-- zuul/cmd/client.py | 2 +- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst index 7519f64689..5e0684c36a 100644 --- a/doc/source/authentication.rst +++ b/doc/source/authentication.rst @@ -129,8 +129,8 @@ For example, in Python, and for an authenticator using the ``HS256`` algorithm: >>> jwt.encode({'sub': 'user1', 'iss': , 'aud': , - 'iat': time.time(), - 'exp': time.time() + 300, + 'iat': int(time.time()), + 'exp': int(time.time()) + 300, 'zuul': { 'admin': ['tenant-one'] } diff --git a/requirements.txt b/requirements.txt index 88eed57cd0..a4aeb7f09f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ alembic cryptography>=1.6 cachecontrol<0.12.7 cachetools -pyjwt>=2.0.0,<2.6.0 +pyjwt>=2.0.0 iso8601 psutil fb-re2>=1.0.6 diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index 74fdf1800b..95ccdb4a21 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -92,7 +92,7 @@ class TestOpenIDConnectAuthenticator(BaseTestCase): payload = { 'iss': FAKE_WELL_KNOWN_CONFIG['issuer'], 'aud': config['client_id'], - 'exp': time.time() + 3600, + 'exp': int(time.time()) + 3600, 'sub': 'someone' } token = jwt.encode( diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py index 3145d4a0a3..83f43882a5 100644 --- a/tests/unit/test_web.py +++ b/tests/unit/test_web.py @@ -2063,7 +2063,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='OnlyZuulNoDana', algorithm='HS256') resp = self.post_url( @@ -2125,7 +2125,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() - 3600} + 'exp': int(time.time()) - 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') resp = self.post_url( @@ -2160,7 +2160,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-six', 'tenant-ten', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') resp = self.post_url( @@ -2194,7 +2194,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'aud': 'zuul.example.com', 'sub': 'testuser', 'zuul': {'admin': ['tenant-one', ]}, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} args = {"reason": "some reason", "count": 1, 'job': 'project-test2', @@ -2234,7 +2234,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.post_url( @@ -2286,7 +2286,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} request_id, _ = self._init_autohold_delete(authz) # now try the autohold-delete API bad_authz = {'iss': 'zuul_operator', @@ -2295,7 +2295,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-two', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} bad_token = jwt.encode(bad_authz, key='NoDanaOnlyZuul', algorithm='HS256') resp = self.delete_url( @@ -2313,7 +2313,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} bad_token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') resp = self.delete_url( @@ -2328,7 +2328,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} request_id, token = self._init_autohold_delete(authz) resp = self.delete_url( "api/tenant/tenant-one/autohold/%s" % request_id, @@ -2352,7 +2352,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') path = "api/tenant/%(tenant)s/project/%(project)s/enqueue" @@ -2404,7 +2404,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.post_url(path % enqueue_args, @@ -2448,7 +2448,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') path = "api/tenant/%(tenant)s/project/%(project)s/dequeue" @@ -2565,8 +2565,8 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 3600, - 'iat': time.time()} + 'exp': int(time.time()) + 3600, + 'iat': int(time.time())} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.post_url( @@ -2654,8 +2654,8 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 3600, - 'iat': time.time()} + 'exp': int(time.time()) + 3600, + 'iat': int(time.time())} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.post_url( @@ -2746,8 +2746,8 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 3600, - 'iat': time.time()} + 'exp': int(time.time()) + 3600, + 'iat': int(time.time())} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.post_url( @@ -2795,7 +2795,7 @@ class TestTenantScopedWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one'], }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.get_url( @@ -2838,7 +2838,7 @@ class TestTenantScopedWebApiWithAuthRules(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.post_url( @@ -2877,21 +2877,21 @@ class TestTenantScopedWebApiWithAuthRules(BaseTestWeb): authz = {'iss': 'zuul_operator', 'aud': 'zuul.example.com', 'sub': 'venkman', - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} _test_project_enqueue_with_authz(i, p, authz, 200) i += 1 # Unauthorized sub authz = {'iss': 'zuul_operator', 'aud': 'zuul.example.com', 'sub': 'vigo', - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} _test_project_enqueue_with_authz(i, p, authz, 403) i += 1 # unauthorized issuer authz = {'iss': 'columbia.edu', 'aud': 'zuul.example.com', 'sub': 'stantz', - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} _test_project_enqueue_with_authz(i, p, authz, 401) self.waitUntilSettled() @@ -2905,7 +2905,7 @@ class TestTenantScopedWebApiWithAuthRules(BaseTestWeb): 'aud': 'zuul.example.com', 'sub': 'melnitz', 'groups': ['ghostbusters', 'secretary'], - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') path = "api/tenant/%(tenant)s/project/%(project)s/enqueue" @@ -2931,7 +2931,7 @@ class TestTenantScopedWebApiWithAuthRules(BaseTestWeb): 'sub': 'zeddemore', 'vehicle': { 'car': 'ecto-1'}, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') path = "api/tenant/%(tenant)s/project/%(project)s/enqueue" @@ -2953,7 +2953,7 @@ class TestTenantScopedWebApiWithAuthRules(BaseTestWeb): 'aud': 'zuul.example.com', 'sub': 'testuser', 'zuul': {'admin': admin_tenants}, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.get_url('/api/tenant/tenant-one/authorizations', @@ -2991,7 +2991,7 @@ class TestTenantScopedWebApiWithAuthRules(BaseTestWeb): for test_user in users: authz = test_user['authz'] - authz['exp'] = time.time() + 3600 + authz['exp'] = int(time.time()) + 3600 token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.get_url('/api/tenant/tenant-one/authorizations', @@ -3031,7 +3031,7 @@ class TestTenantScopedWebApiTokenWithExpiry(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') resp = self.post_url( @@ -3066,8 +3066,8 @@ class TestTenantScopedWebApiTokenWithExpiry(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 7200, - 'iat': time.time() + 3600} + 'exp': int(time.time()) + 7200, + 'iat': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') resp = self.post_url( @@ -3102,8 +3102,8 @@ class TestTenantScopedWebApiTokenWithExpiry(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 3600, - 'iat': time.time()} + 'exp': int(time.time()) + 3600, + 'iat': int(time.time())} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') time.sleep(10) @@ -3146,8 +3146,8 @@ class TestTenantScopedWebApiTokenWithExpiry(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ], }, - 'exp': time.time() + 3600, - 'iat': time.time()} + 'exp': int(time.time()) + 3600, + 'iat': int(time.time())} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') req = self.post_url( @@ -3249,7 +3249,7 @@ class TestCLIViaWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -3288,7 +3288,7 @@ class TestCLIViaWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -3317,7 +3317,7 @@ class TestCLIViaWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -3356,7 +3356,7 @@ class TestCLIViaWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -3407,7 +3407,7 @@ class TestCLIViaWebApi(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( diff --git a/tests/zuul_client/test_zuulclient.py b/tests/zuul_client/test_zuulclient.py index 9d381c99b2..b2ac6ed086 100644 --- a/tests/zuul_client/test_zuulclient.py +++ b/tests/zuul_client/test_zuulclient.py @@ -187,7 +187,7 @@ class TestZuulClientAdmin(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -227,7 +227,7 @@ class TestZuulClientAdmin(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -263,7 +263,7 @@ class TestZuulClientAdmin(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -308,7 +308,7 @@ class TestZuulClientAdmin(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( @@ -359,7 +359,7 @@ class TestZuulClientAdmin(BaseTestWeb): 'zuul': { 'admin': ['tenant-one', ] }, - 'exp': time.time() + 3600} + 'exp': int(time.time()) + 3600} token = jwt.encode(authz, key='NoDanaOnlyZuul', algorithm='HS256') p = subprocess.Popen( diff --git a/zuul/cmd/client.py b/zuul/cmd/client.py index 98dd56b9de..031b10a1e1 100755 --- a/zuul/cmd/client.py +++ b/zuul/cmd/client.py @@ -735,7 +735,7 @@ class Client(zuul.cmd.ZuulApp): print('"%s" authenticator configuration not found.' % self.args.auth_config) sys.exit(1) - now = time.time() + now = int(time.time()) token = {'iat': now, 'exp': now + self.args.expires_in, 'iss': get_default(self.config, auth_section, 'issuer_id'),