Skip to content

Commit 1f7c9d1

Browse files
committed
fix: add user secret key when using saved secrets (#519)
1 parent e8accdc commit 1f7c9d1

4 files changed

Lines changed: 51 additions & 9 deletions

File tree

components/renku_data_services/notebooks/api/schemas/cloud_storage.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,24 @@ def secret(
151151
namespace: str,
152152
labels: dict[str, str] | None = None,
153153
annotations: dict[str, str] | None = None,
154+
user_secret_key: str | None = None,
154155
) -> client.V1Secret:
155156
"""The secret containing the configuration for the rclone csi driver."""
157+
string_data = {
158+
"remote": self.name or base_name,
159+
"remotePath": self.source_path,
160+
"configData": self.config_string(self.name or base_name),
161+
}
162+
if user_secret_key:
163+
string_data["secretKey"] = user_secret_key
156164
return client.V1Secret(
157165
metadata=client.V1ObjectMeta(
158166
name=base_name,
159167
namespace=namespace,
160168
annotations=annotations,
161169
labels={"name": base_name} | (labels or {}),
162170
),
163-
string_data={
164-
"remote": self.name or base_name,
165-
"remotePath": self.source_path,
166-
"configData": self.config_string(self.name or base_name),
167-
},
171+
string_data=string_data,
168172
)
169173

170174
def get_manifest_patch(

components/renku_data_services/notebooks/blueprints.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
renku_2_make_server_name,
7272
)
7373
from renku_data_services.notebooks.utils import (
74+
get_user_secret,
7475
merge_node_affinities,
7576
node_affinity_from_resource_class,
7677
tolerations_from_resource_class,
@@ -342,9 +343,26 @@ async def _handler(
342343
secrets_to_create: list[V1Secret] = []
343344
# Generate the cloud starge secrets
344345
data_sources: list[DataSource] = []
346+
user_secret_key: str | None = None
347+
if isinstance(user, AuthenticatedAPIUser) and len(dcs_secrets) > 0:
348+
user_secret_key = await get_user_secret(self.nb_config.data_service_url, user)
345349
for cs_id, cs in dcs.items():
346350
secret_name = f"{server_name}-ds-{cs_id.lower()}"
347-
secrets_to_create.append(cs.secret(secret_name, self.nb_config.k8s_client.preferred_namespace))
351+
secret_key_needed = len(dcs_secrets.get(cs_id, [])) > 0
352+
if secret_key_needed and user_secret_key is None:
353+
raise errors.ProgrammingError(
354+
message=f"You have saved storage secrets for data connector {cs_id} "
355+
f"associated with your user ID {user.id} but no key to decrypt them, "
356+
"therefore we cannot mount the requested data connector. "
357+
"Please report this to the renku administrators."
358+
)
359+
secrets_to_create.append(
360+
cs.secret(
361+
secret_name,
362+
self.nb_config.k8s_client.preferred_namespace,
363+
user_secret_key=user_secret_key if secret_key_needed else None,
364+
)
365+
)
348366
data_sources.append(
349367
DataSource(
350368
mountPath=cs.mount_folder,

components/renku_data_services/notebooks/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""Utilities for notebooks."""
22

3+
import httpx
4+
35
import renku_data_services.crc.models as crc_models
6+
from renku_data_services.base_models.core import AuthenticatedAPIUser
47
from renku_data_services.notebooks.crs import (
58
MatchExpression,
69
NodeAffinity,
@@ -10,6 +13,7 @@
1013
RequiredDuringSchedulingIgnoredDuringExecution,
1114
Toleration,
1215
)
16+
from renku_data_services.utils.cryptography import get_encryption_key
1317

1418

1519
def merge_node_affinities(
@@ -95,3 +99,18 @@ def tolerations_from_resource_class(resource_class: crc_models.ResourceClass) ->
9599
for tol in resource_class.tolerations:
96100
output.append(Toleration(key=tol, operator="Exists"))
97101
return output
102+
103+
104+
async def get_user_secret(data_svc_url: str, user: AuthenticatedAPIUser) -> str | None:
105+
"""Get the user secret key from the secret service."""
106+
107+
async with httpx.AsyncClient(timeout=5) as client:
108+
response = await client.get(
109+
f"{data_svc_url}/user/secret_key",
110+
headers={"Authorization": f"Bearer {user.access_token}"},
111+
)
112+
if response.status_code != 200:
113+
return None
114+
user_key = response.json()
115+
116+
return get_encryption_key(user_key["secret_key"].encode(), user.id.encode()).decode("utf-8")

components/renku_data_services/utils/cryptography.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
99

1010

11-
def _get_encryption_key(password: bytes, salt: bytes) -> bytes:
11+
def get_encryption_key(password: bytes, salt: bytes) -> bytes:
12+
"""Create an encryption key with the password and salt."""
1213
kdf = PBKDF2HMAC(
1314
algorithm=hashes.SHA256(),
1415
length=32,
@@ -25,13 +26,13 @@ def generate_random_encryption_key() -> bytes:
2526

2627
def encrypt_string(password: bytes, salt: str, data: str) -> bytes:
2728
"""Encrypt a given string."""
28-
key = _get_encryption_key(password=password, salt=salt.encode())
29+
key = get_encryption_key(password=password, salt=salt.encode())
2930
return Fernet(key).encrypt(data.encode())
3031

3132

3233
def decrypt_string(password: bytes, salt: str, data: bytes) -> str:
3334
"""Decrypt a given string."""
34-
key = _get_encryption_key(password=password, salt=salt.encode())
35+
key = get_encryption_key(password=password, salt=salt.encode())
3536
return Fernet(key).decrypt(data).decode()
3637

3738

0 commit comments

Comments
 (0)