Skip to content

Commit 500c6d1

Browse files
committed
Add client side logic for creating data bag for secrets
1 parent f91507a commit 500c6d1

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

juju/secrets.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Copyright 2023 Canonical Ltd.
2+
# Licensed under the Apache V2, see LICENCE file for details.
3+
4+
"""
5+
This module contains utility logic for secrets such as reading secret data from yaml and creating data bag for secrets.
6+
"""
7+
8+
9+
import base64
10+
from pathlib import Path
11+
import re
12+
13+
file_suffix = "#file"
14+
max_value_size_bytes = 5 * 1024
15+
max_content_size_bytes = 64 * 1024
16+
17+
18+
def create_secret_data(args):
19+
"""CreateSecretData creates a secret data bag from a list of arguments.
20+
If a key has the #base64 suffix, then the value is already base64 encoded,otherwise the value is base64 encoded as it is added to the data bag.
21+
22+
If a key has the '#file' suffix, the value is read from the corresponding file.
23+
24+
:return []str: bag of key value pairs for a secret
25+
"""
26+
data = {}
27+
for val in args:
28+
# Remove any base64 padding ("=") before splitting the key=value.
29+
stripped = val.rstrip(base64.b64encode(b'=').decode('utf-8'))
30+
idx = stripped.find("=")
31+
if idx < 1:
32+
raise ValueError(f"Invalid key value {val}")
33+
34+
key = stripped[0:idx]
35+
value = stripped[idx + 1:]
36+
37+
# If the key doesn't have the #file suffix, then add it to the bag and continue.
38+
if not key.endswith(file_suffix):
39+
data[key] = value
40+
continue
41+
42+
key = key.rstrip(file_suffix)
43+
path = Path(value).resolve()
44+
try:
45+
fs = path.stat()
46+
if fs.st_size > max_value_size_bytes:
47+
raise ValueError(f"Secret content in file {path} too large: {fs.st_size} bytes")
48+
content = path.read_text()
49+
data[key] = content
50+
except Exception as e:
51+
raise ValueError(f"Error processing key {key}: {e}")
52+
53+
return encodeValuesBase64(data)
54+
55+
56+
def read_secret_data(file):
57+
"""ReadSecretData reads secret data from a YAML or JSON file as key value pairs.
58+
59+
:param file str: Path to a YAML or JSON file to read values from.
60+
61+
:return []str: bag of key value pairs for a secret
62+
"""
63+
return {}
64+
65+
66+
base64_suffix = "#base64"
67+
key_reg_exp = re.compile("^([a-z](?:-?[a-z0-9]){2,})$")
68+
69+
70+
def encodeValuesBase64(data):
71+
"""Encodes the values in the given data bag for a secret
72+
73+
If a key has the #base64 suffix, then the value is already base64 encoded,otherwise the value is base64 encoded as it is added to the data bag.
74+
"""
75+
76+
out = {}
77+
content_size = 0
78+
for k, v in data.items():
79+
if len(v) > max_value_size_bytes:
80+
raise ValueError(f"secret content for key {k} too large: {len(v)} bytes")
81+
content_size += len(v)
82+
if k.endswith(base64_suffix):
83+
k = k[:-7]
84+
if not key_reg_exp.match(k):
85+
raise ValueError(f"Not valid key: {k}")
86+
out[k] = v
87+
continue
88+
if not key_reg_exp.match(k):
89+
raise ValueError(f"Not valid key: {k}")
90+
out[k] = base64.b64encode(str(v).encode()).decode()
91+
if content_size > max_content_size_bytes:
92+
raise ValueError(f"secret content too large: {content_size} bytes")
93+
return out

0 commit comments

Comments
 (0)