From 1174a00e5e83c2631836a4179762d0bc78219329 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Wed, 27 Mar 2024 22:09:59 +0000 Subject: [PATCH 1/4] Validate the Kubespawner secret is correctly encoded as base64 --- tests/test_spawner.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_spawner.py b/tests/test_spawner.py index 9daa7cdc..efb3d1af 100644 --- a/tests/test_spawner.py +++ b/tests/test_spawner.py @@ -1,4 +1,5 @@ import asyncio +import base64 import json import os from functools import partial @@ -365,6 +366,29 @@ async def test_spawn_component_label( await spawner.stop() +async def test_spawner_internal_ssl_secret( + ssl_app, + config, +): + spawner = KubeSpawner( + config=config, + user=MockUser(name="ssl"), + internal_ssl=True, + internal_trust_bundles=ssl_app.internal_trust_bundles, + internal_certs_location=ssl_app.internal_certs_location, + _mock=True, + ) + # initialize ssl config + hub_paths = await spawner.create_certs() + + spawner.cert_paths = await spawner.move_certs(hub_paths) + + # Validate that certificates were correctly encoded as base64 + manifest = spawner.get_secret_manifest(None) + for _, secret in manifest.data.items(): + base64.b64decode(secret, validate=True) + + async def test_spawn_internal_ssl( kube_ns, kube_client, From 2b95b138cecc877871cbe5dff72aef0084c85bf5 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Thu, 28 Mar 2024 16:23:27 +0000 Subject: [PATCH 2/4] Concatenate SSL CAs before encoding Otherwise you can end up with `==` padding in the middle of the encoded secret data, which is invalid base64. --- kubespawner/objects.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/kubespawner/objects.py b/kubespawner/objects.py index f644811f..9e952747 100644 --- a/kubespawner/objects.py +++ b/kubespawner/objects.py @@ -1014,16 +1014,11 @@ def make_secret( encoded = base64.b64encode(file.read().encode("utf-8")) secret.data['ssl.crt'] = encoded.decode("utf-8") - with open(cert_paths['cafile']) as file: - encoded = base64.b64encode(file.read().encode("utf-8")) + with open(cert_paths['cafile']) as ca_file, open(hub_ca) as hub_ca_file: + cas = ca_file.read().strip("\n") + "\n" + hub_ca_file.read() + encoded = base64.b64encode(cas.encode("utf-8")) secret.data["notebooks-ca_trust.crt"] = encoded.decode("utf-8") - with open(hub_ca) as file: - encoded = base64.b64encode(file.read().encode("utf-8")) - secret.data["notebooks-ca_trust.crt"] = secret.data[ - "notebooks-ca_trust.crt" - ] + encoded.decode("utf-8") - return secret From e75d1cb9b68ca528ac8ac4cd10773b5c3b55f78f Mon Sep 17 00:00:00 2001 From: Simon Li Date: Thu, 28 Mar 2024 16:26:02 +0000 Subject: [PATCH 3/4] Refer to GGH issue/PR in test_spawner_internal_ssl_secret --- tests/test_spawner.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_spawner.py b/tests/test_spawner.py index efb3d1af..432aefd9 100644 --- a/tests/test_spawner.py +++ b/tests/test_spawner.py @@ -370,6 +370,10 @@ async def test_spawner_internal_ssl_secret( ssl_app, config, ): + """ + Validate that certificates are correctly encoded as base64 + https://github.com/jupyterhub/kubespawner/pull/828 + """ spawner = KubeSpawner( config=config, user=MockUser(name="ssl"), @@ -383,7 +387,6 @@ async def test_spawner_internal_ssl_secret( spawner.cert_paths = await spawner.move_certs(hub_paths) - # Validate that certificates were correctly encoded as base64 manifest = spawner.get_secret_manifest(None) for _, secret in manifest.data.items(): base64.b64decode(secret, validate=True) From 58520f0e765a622a9291c885ed1e75b1e16af8df Mon Sep 17 00:00:00 2001 From: Simon Li Date: Thu, 28 Mar 2024 16:48:36 +0000 Subject: [PATCH 4/4] Include test_spawner_internal_ssl_secret in test_spawn_internal_ssl --- tests/test_spawner.py | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/tests/test_spawner.py b/tests/test_spawner.py index 432aefd9..be2cecb1 100644 --- a/tests/test_spawner.py +++ b/tests/test_spawner.py @@ -366,32 +366,6 @@ async def test_spawn_component_label( await spawner.stop() -async def test_spawner_internal_ssl_secret( - ssl_app, - config, -): - """ - Validate that certificates are correctly encoded as base64 - https://github.com/jupyterhub/kubespawner/pull/828 - """ - spawner = KubeSpawner( - config=config, - user=MockUser(name="ssl"), - internal_ssl=True, - internal_trust_bundles=ssl_app.internal_trust_bundles, - internal_certs_location=ssl_app.internal_certs_location, - _mock=True, - ) - # initialize ssl config - hub_paths = await spawner.create_certs() - - spawner.cert_paths = await spawner.move_certs(hub_paths) - - manifest = spawner.get_secret_manifest(None) - for _, secret in manifest.data.items(): - base64.b64decode(secret, validate=True) - - async def test_spawn_internal_ssl( kube_ns, kube_client, @@ -418,6 +392,12 @@ async def test_spawn_internal_ssl( spawner.cert_paths = await spawner.move_certs(hub_paths) + # Validate that certificates are correctly encoded + # https://github.com/jupyterhub/kubespawner/pull/828 + manifest = spawner.get_secret_manifest(None) + for _, secret in manifest.data.items(): + base64.b64decode(secret, validate=True) + # start the spawner url = await spawner.start() pod_name = "jupyter-%s" % spawner.user.name