From 8bee2f85a21e862ecb489c4188d2155651290630 Mon Sep 17 00:00:00 2001 From: Ben Picolo Date: Mon, 30 Jul 2018 14:23:18 -0400 Subject: [PATCH 1/5] Fix base64 padding for kube config --- config/kube_config.py | 6 ++++-- config/kube_config_test.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/config/kube_config.py b/config/kube_config.py index 958959e3..1cbb80ea 100644 --- a/config/kube_config.py +++ b/config/kube_config.py @@ -264,13 +264,15 @@ def _load_oid_token(self, provider): if len(parts) != 3: # Not a valid JWT return None + padding = (4 - len(parts[1]) % 4) * '=' + if PY3: jwt_attributes = json.loads( - base64.b64decode(parts[1]).decode('utf-8') + base64.b64decode(parts[1] + padding).decode('utf-8') ) else: jwt_attributes = json.loads( - base64.b64decode(parts[1] + "==") + base64.b64decode(parts[1] + padding) ) expire = jwt_attributes.get('exp') diff --git a/config/kube_config_test.py b/config/kube_config_test.py index ae9dc225..9b86f232 100644 --- a/config/kube_config_test.py +++ b/config/kube_config_test.py @@ -47,6 +47,10 @@ def _base64(string): return base64.standard_b64encode(string.encode()).decode() +def _unpadded_base64(string): + return base64.b64encode(string.encode()).decode().rstrip('=') + + def _format_expiry_datetime(dt): return dt.strftime(EXPIRY_DATETIME_FORMAT) @@ -94,11 +98,13 @@ def _raise_exception(st): TEST_OIDC_TOKEN = "test-oidc-token" TEST_OIDC_INFO = "{\"name\": \"test\"}" -TEST_OIDC_BASE = _base64(TEST_OIDC_TOKEN) + "." + _base64(TEST_OIDC_INFO) +TEST_OIDC_BASE = _unpadded_base64( + TEST_OIDC_TOKEN) + "." + _unpadded_base64(TEST_OIDC_INFO) TEST_OIDC_LOGIN = TEST_OIDC_BASE + "." + TEST_CLIENT_CERT_BASE64 TEST_OIDC_TOKEN = "Bearer %s" % TEST_OIDC_LOGIN TEST_OIDC_EXP = "{\"name\": \"test\",\"exp\": 536457600}" -TEST_OIDC_EXP_BASE = _base64(TEST_OIDC_TOKEN) + "." + _base64(TEST_OIDC_EXP) +TEST_OIDC_EXP_BASE = _unpadded_base64( + TEST_OIDC_TOKEN) + "." + _unpadded_base64(TEST_OIDC_EXP) TEST_OIDC_EXPIRED_LOGIN = TEST_OIDC_EXP_BASE + "." + TEST_CLIENT_CERT_BASE64 TEST_OIDC_CA = _base64(TEST_CERTIFICATE_AUTH) From ba8b9e011f452b42e0c4b95dd2d19ed8fedce110 Mon Sep 17 00:00:00 2001 From: Ben Picolo Date: Mon, 18 Feb 2019 11:16:07 -0500 Subject: [PATCH 2/5] Add additional checks + test case fixes --- config/kube_config.py | 13 ++++++++++++- config/kube_config_test.py | 24 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/config/kube_config.py b/config/kube_config.py index 1cbb80ea..46b55b58 100644 --- a/config/kube_config.py +++ b/config/kube_config.py @@ -259,12 +259,23 @@ def _load_oid_token(self, provider): if 'config' not in provider: return - parts = provider['config']['id-token'].split('.') + reserved_characters = frozenset(["=", "+", "/"]) + token = provider['config']['id-token'] + if any(char in token for char in reserved_characters): + # Invalid jwt, as it contains url-unsafe chars + return None + + parts = token.split('.') if len(parts) != 3: # Not a valid JWT return None padding = (4 - len(parts[1]) % 4) * '=' + if len(padding) == 3: + # According to spec, 3 padding characters cannot occur + # in a valid jwt + # https://tools.ietf.org/html/rfc7515#appendix-C + return None if PY3: jwt_attributes = json.loads( diff --git a/config/kube_config_test.py b/config/kube_config_test.py index 9b86f232..b482c7a9 100644 --- a/config/kube_config_test.py +++ b/config/kube_config_test.py @@ -47,8 +47,8 @@ def _base64(string): return base64.standard_b64encode(string.encode()).decode() -def _unpadded_base64(string): - return base64.b64encode(string.encode()).decode().rstrip('=') +def _urlsafe_unpadded_b64encode(string): + return base64.urlsafe_b64encode(string.encode()).decode().rstrip('=') def _format_expiry_datetime(dt): @@ -98,14 +98,22 @@ def _raise_exception(st): TEST_OIDC_TOKEN = "test-oidc-token" TEST_OIDC_INFO = "{\"name\": \"test\"}" -TEST_OIDC_BASE = _unpadded_base64( - TEST_OIDC_TOKEN) + "." + _unpadded_base64(TEST_OIDC_INFO) -TEST_OIDC_LOGIN = TEST_OIDC_BASE + "." + TEST_CLIENT_CERT_BASE64 +TEST_OIDC_BASE = ".".join([ + _urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN), + _urlsafe_unpadded_b64encode(TEST_OIDC_INFO) +]) +TEST_OIDC_LOGIN = ".".join([ + TEST_OIDC_BASE, + _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT_BASE64) +]) TEST_OIDC_TOKEN = "Bearer %s" % TEST_OIDC_LOGIN TEST_OIDC_EXP = "{\"name\": \"test\",\"exp\": 536457600}" -TEST_OIDC_EXP_BASE = _unpadded_base64( - TEST_OIDC_TOKEN) + "." + _unpadded_base64(TEST_OIDC_EXP) -TEST_OIDC_EXPIRED_LOGIN = TEST_OIDC_EXP_BASE + "." + TEST_CLIENT_CERT_BASE64 +TEST_OIDC_EXP_BASE = _urlsafe_unpadded_b64encode( + TEST_OIDC_TOKEN) + "." + _urlsafe_unpadded_b64encode(TEST_OIDC_EXP) +TEST_OIDC_EXPIRED_LOGIN = ".".join([ + TEST_OIDC_EXP_BASE, + _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT) +]) TEST_OIDC_CA = _base64(TEST_CERTIFICATE_AUTH) From 2202bb5c516e229538993c1243e7f5f1b5697587 Mon Sep 17 00:00:00 2001 From: Ben Picolo Date: Tue, 19 Feb 2019 18:28:50 -0500 Subject: [PATCH 3/5] Add tests for updated pieces --- config/kube_config.py | 6 +-- config/kube_config_test.py | 79 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/config/kube_config.py b/config/kube_config.py index 46b55b58..019c947a 100644 --- a/config/kube_config.py +++ b/config/kube_config.py @@ -264,18 +264,18 @@ def _load_oid_token(self, provider): if any(char in token for char in reserved_characters): # Invalid jwt, as it contains url-unsafe chars - return None + return parts = token.split('.') if len(parts) != 3: # Not a valid JWT - return None + return padding = (4 - len(parts[1]) % 4) * '=' if len(padding) == 3: # According to spec, 3 padding characters cannot occur # in a valid jwt # https://tools.ietf.org/html/rfc7515#appendix-C - return None + return if PY3: jwt_attributes = json.loads( diff --git a/config/kube_config_test.py b/config/kube_config_test.py index b482c7a9..0deec795 100644 --- a/config/kube_config_test.py +++ b/config/kube_config_test.py @@ -114,6 +114,17 @@ def _raise_exception(st): TEST_OIDC_EXP_BASE, _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT) ]) +TEST_OIDC_CONTAINS_RESERVED_CHARACTERS = ".".join([ + _urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN), + _urlsafe_unpadded_b64encode(TEST_OIDC_INFO).replace("a", "+"), + _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT) +]) +TEST_OIDC_INVALID_PADDING_LENGTH = ".".join([ + _urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN), + "aaaaa", + _urlsafe_unpadded_b64encode(TEST_CLIENT_CERT) +]) + TEST_OIDC_CA = _base64(TEST_CERTIFICATE_AUTH) @@ -420,6 +431,22 @@ class TestKubeConfigLoader(BaseTestCase): "user": "expired_oidc_nocert" } }, + { + "name": "oidc_contains_reserved_character", + "context": { + "cluster": "default", + "user": "oidc_contains_reserved_character" + + } + }, + { + "name": "oidc_invalid_padding_length", + "context": { + "cluster": "default", + "user": "oidc_invalid_padding_length" + + } + }, { "name": "user_pass", "context": { @@ -606,6 +633,38 @@ class TestKubeConfigLoader(BaseTestCase): } } }, + { + "name": "oidc_contains_reserved_character", + "user": { + "auth-provider": { + "name": "oidc", + "config": { + "client-id": "tectonic-kubectl", + "client-secret": "FAKE_SECRET", + "id-token": TEST_OIDC_CONTAINS_RESERVED_CHARACTERS, + "idp-issuer-url": "https://example.org/identity", + "refresh-token": + "lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk" + } + } + } + }, + { + "name": "oidc_invalid_padding_length", + "user": { + "auth-provider": { + "name": "oidc", + "config": { + "client-id": "tectonic-kubectl", + "client-secret": "FAKE_SECRET", + "id-token": TEST_OIDC_INVALID_PADDING_LENGTH, + "idp-issuer-url": "https://example.org/identity", + "refresh-token": + "lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk" + } + } + } + }, { "name": "user_pass", "user": { @@ -804,6 +863,26 @@ def test_oidc_with_refresh_nocert( self.assertTrue(loader._load_auth_provider_token()) self.assertEqual("Bearer abc123", loader.token) + def test_oidc_fails_if_contains_reserved_chars(self): + loader = KubeConfigLoader( + config_dict=self.TEST_KUBE_CONFIG, + active_context="oidc_contains_reserved_character", + ) + self.assertEqual( + loader._load_oid_token("oidc_contains_reserved_character"), + None, + ) + + def test_oidc_fails_if_invalid_padding_length(self): + loader = KubeConfigLoader( + config_dict=self.TEST_KUBE_CONFIG, + active_context="oidc_invalid_padding_length", + ) + self.assertEqual( + loader._load_oid_token("oidc_invalid_padding_length"), + None, + ) + def test_user_pass(self): expected = FakeConfig(host=TEST_HOST, token=TEST_BASIC_TOKEN) actual = FakeConfig() From d492cd3f3e8d548caeb3858bdbe6539c3382373c Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Tue, 30 Jul 2019 14:12:52 -0700 Subject: [PATCH 4/5] use upstream release-8.0 in travis as a workaround for adal requirement --- run_tox.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/run_tox.sh b/run_tox.sh index 55733785..122a7a4a 100755 --- a/run_tox.sh +++ b/run_tox.sh @@ -36,6 +36,7 @@ popd > /dev/null cd "${TMP_DIR}" git clone https://github.com/kubernetes-client/python.git cd python +git checkout release-8.0 git config user.email "kubernetes-client@k8s.com" git config user.name "kubenetes client" git rm -rf kubernetes/base From 4017a9de05771f2457e4d0a9ea4ae1b6cc5fc895 Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Tue, 30 Jul 2019 15:02:10 -0700 Subject: [PATCH 5/5] upstream release-8.0 wasn't ready for py37 and we didn't release py37 package --- .travis.yml | 4 ---- tox.ini | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 18dcd2fd..c3fefd02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,10 +25,6 @@ matrix: env: TOXENV=py36 - python: 3.6 env: TOXENV=py36-functional - - python: 3.7 - env: TOXENV=py37 - - python: 3.7 - env: TOXENV=py37-functional install: - pip install tox diff --git a/tox.ini b/tox.ini index f935a6cd..f36f3478 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] skipsdist = True -envlist = py27, py34, py35, py36, py37 +envlist = py27, py34, py35, py36 [testenv] passenv = TOXENV CI TRAVIS TRAVIS_*