Skip to content
This repository was archived by the owner on Mar 13, 2022. It is now read-only.

Commit 54a0210

Browse files
authored
Merge pull request #146 from roycaihw/automated-cherry-pick-of-#79-upstream-release-9.0-base64-patch
Automated cherry pick of #79
2 parents 2d69e89 + b013697 commit 54a0210

File tree

2 files changed

+114
-8
lines changed

2 files changed

+114
-8
lines changed

config/kube_config.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -268,18 +268,31 @@ def _load_oid_token(self, provider):
268268
if 'config' not in provider:
269269
return
270270

271-
parts = provider['config']['id-token'].split('.')
271+
reserved_characters = frozenset(["=", "+", "/"])
272+
token = provider['config']['id-token']
272273

274+
if any(char in token for char in reserved_characters):
275+
# Invalid jwt, as it contains url-unsafe chars
276+
return
277+
278+
parts = token.split('.')
273279
if len(parts) != 3: # Not a valid JWT
274-
return None
280+
return
281+
282+
padding = (4 - len(parts[1]) % 4) * '='
283+
if len(padding) == 3:
284+
# According to spec, 3 padding characters cannot occur
285+
# in a valid jwt
286+
# https://tools.ietf.org/html/rfc7515#appendix-C
287+
return
275288

276289
if PY3:
277290
jwt_attributes = json.loads(
278-
base64.b64decode(parts[1]).decode('utf-8')
291+
base64.b64decode(parts[1] + padding).decode('utf-8')
279292
)
280293
else:
281294
jwt_attributes = json.loads(
282-
base64.b64decode(parts[1] + "==")
295+
base64.b64decode(parts[1] + padding)
283296
)
284297

285298
expire = jwt_attributes.get('exp')

config/kube_config_test.py

+97-4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ def _base64(string):
4949
return base64.standard_b64encode(string.encode()).decode()
5050

5151

52+
def _urlsafe_unpadded_b64encode(string):
53+
return base64.urlsafe_b64encode(string.encode()).decode().rstrip('=')
54+
55+
5256
def _format_expiry_datetime(dt):
5357
return dt.strftime(EXPIRY_DATETIME_FORMAT)
5458

@@ -96,12 +100,33 @@ def _raise_exception(st):
96100

97101
TEST_OIDC_TOKEN = "test-oidc-token"
98102
TEST_OIDC_INFO = "{\"name\": \"test\"}"
99-
TEST_OIDC_BASE = _base64(TEST_OIDC_TOKEN) + "." + _base64(TEST_OIDC_INFO)
100-
TEST_OIDC_LOGIN = TEST_OIDC_BASE + "." + TEST_CLIENT_CERT_BASE64
103+
TEST_OIDC_BASE = ".".join([
104+
_urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN),
105+
_urlsafe_unpadded_b64encode(TEST_OIDC_INFO)
106+
])
107+
TEST_OIDC_LOGIN = ".".join([
108+
TEST_OIDC_BASE,
109+
_urlsafe_unpadded_b64encode(TEST_CLIENT_CERT_BASE64)
110+
])
101111
TEST_OIDC_TOKEN = "Bearer %s" % TEST_OIDC_LOGIN
102112
TEST_OIDC_EXP = "{\"name\": \"test\",\"exp\": 536457600}"
103-
TEST_OIDC_EXP_BASE = _base64(TEST_OIDC_TOKEN) + "." + _base64(TEST_OIDC_EXP)
104-
TEST_OIDC_EXPIRED_LOGIN = TEST_OIDC_EXP_BASE + "." + TEST_CLIENT_CERT_BASE64
113+
TEST_OIDC_EXP_BASE = _urlsafe_unpadded_b64encode(
114+
TEST_OIDC_TOKEN) + "." + _urlsafe_unpadded_b64encode(TEST_OIDC_EXP)
115+
TEST_OIDC_EXPIRED_LOGIN = ".".join([
116+
TEST_OIDC_EXP_BASE,
117+
_urlsafe_unpadded_b64encode(TEST_CLIENT_CERT)
118+
])
119+
TEST_OIDC_CONTAINS_RESERVED_CHARACTERS = ".".join([
120+
_urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN),
121+
_urlsafe_unpadded_b64encode(TEST_OIDC_INFO).replace("a", "+"),
122+
_urlsafe_unpadded_b64encode(TEST_CLIENT_CERT)
123+
])
124+
TEST_OIDC_INVALID_PADDING_LENGTH = ".".join([
125+
_urlsafe_unpadded_b64encode(TEST_OIDC_TOKEN),
126+
"aaaaa",
127+
_urlsafe_unpadded_b64encode(TEST_CLIENT_CERT)
128+
])
129+
105130
TEST_OIDC_CA = _base64(TEST_CERTIFICATE_AUTH)
106131

107132

@@ -408,6 +433,22 @@ class TestKubeConfigLoader(BaseTestCase):
408433
"user": "expired_oidc_nocert"
409434
}
410435
},
436+
{
437+
"name": "oidc_contains_reserved_character",
438+
"context": {
439+
"cluster": "default",
440+
"user": "oidc_contains_reserved_character"
441+
442+
}
443+
},
444+
{
445+
"name": "oidc_invalid_padding_length",
446+
"context": {
447+
"cluster": "default",
448+
"user": "oidc_invalid_padding_length"
449+
450+
}
451+
},
411452
{
412453
"name": "user_pass",
413454
"context": {
@@ -594,6 +635,38 @@ class TestKubeConfigLoader(BaseTestCase):
594635
}
595636
}
596637
},
638+
{
639+
"name": "oidc_contains_reserved_character",
640+
"user": {
641+
"auth-provider": {
642+
"name": "oidc",
643+
"config": {
644+
"client-id": "tectonic-kubectl",
645+
"client-secret": "FAKE_SECRET",
646+
"id-token": TEST_OIDC_CONTAINS_RESERVED_CHARACTERS,
647+
"idp-issuer-url": "https://example.org/identity",
648+
"refresh-token":
649+
"lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk"
650+
}
651+
}
652+
}
653+
},
654+
{
655+
"name": "oidc_invalid_padding_length",
656+
"user": {
657+
"auth-provider": {
658+
"name": "oidc",
659+
"config": {
660+
"client-id": "tectonic-kubectl",
661+
"client-secret": "FAKE_SECRET",
662+
"id-token": TEST_OIDC_INVALID_PADDING_LENGTH,
663+
"idp-issuer-url": "https://example.org/identity",
664+
"refresh-token":
665+
"lucWJjEhlxZW01cXI3YmVlcYnpxNGhzk"
666+
}
667+
}
668+
}
669+
},
597670
{
598671
"name": "user_pass",
599672
"user": {
@@ -792,6 +865,26 @@ def test_oidc_with_refresh_nocert(
792865
self.assertTrue(loader._load_auth_provider_token())
793866
self.assertEqual("Bearer abc123", loader.token)
794867

868+
def test_oidc_fails_if_contains_reserved_chars(self):
869+
loader = KubeConfigLoader(
870+
config_dict=self.TEST_KUBE_CONFIG,
871+
active_context="oidc_contains_reserved_character",
872+
)
873+
self.assertEqual(
874+
loader._load_oid_token("oidc_contains_reserved_character"),
875+
None,
876+
)
877+
878+
def test_oidc_fails_if_invalid_padding_length(self):
879+
loader = KubeConfigLoader(
880+
config_dict=self.TEST_KUBE_CONFIG,
881+
active_context="oidc_invalid_padding_length",
882+
)
883+
self.assertEqual(
884+
loader._load_oid_token("oidc_invalid_padding_length"),
885+
None,
886+
)
887+
795888
def test_user_pass(self):
796889
expected = FakeConfig(host=TEST_HOST, token=TEST_BASIC_TOKEN)
797890
actual = FakeConfig()

0 commit comments

Comments
 (0)