Skip to content

Commit e82446c

Browse files
committed
feat(scheduler): store env vars as secrets in k8s and map them in the RC
This provides more security around sensitive information and lets k8s admins put more controls in place on who can update what Closes #299
1 parent 3fe88f8 commit e82446c

2 files changed

Lines changed: 60 additions & 8 deletions

File tree

rootfs/api/models/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,8 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits):
535535
'memory': release.config.memory,
536536
'cpu': release.config.cpu,
537537
'tags': release.config.tags,
538-
'envs': release.config.values
538+
'envs': release.config.values,
539+
'version': "v{}".format(release.version),
539540
}
540541

541542
try:

rootfs/scheduler/__init__.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,15 @@ def deploy(self, namespace, name, image, command, **kwargs):
377377
new_rc["metadata"]["name"], desired)
378378
)
379379

380-
# Remove old release
380+
# Remove new release
381381
self._scale_rc(namespace, new_rc["metadata"]["name"], 0)
382382
self._delete_rc(namespace, new_rc["metadata"]["name"])
383+
# remove secret that contains env vars for the release
384+
try:
385+
secret_name = "{}-{}-env".format(namespace, kwargs.get('version'))
386+
self._delete_secret(namespace, secret_name)
387+
except KubeHTTPException:
388+
pass
383389

384390
# Bring back old release if available
385391
if old_rc:
@@ -390,6 +396,12 @@ def deploy(self, namespace, name, image, command, **kwargs):
390396
# New release is live and kicking. Clean up old release
391397
if old_rc:
392398
self._delete_rc(namespace, old_rc["metadata"]["name"])
399+
# remove secret that contains env vars for the release
400+
secret_name = "{}-{}-env".format(namespace, old_rc['metadata']['labels']['version'])
401+
try:
402+
self._delete_secret(namespace, secret_name)
403+
except KubeHTTPException:
404+
pass
393405

394406
# Make sure the application is routable and uses the correct port
395407
# Done after the fact to let initial deploy settle before routing
@@ -520,7 +532,7 @@ def run(self, namespace, name, image, entrypoint, command, **kwargs):
520532
containers['command'] = [entrypoint]
521533
containers['args'] = args
522534

523-
self._set_environment(containers, **kwargs)
535+
self._set_environment(containers, namespace, **kwargs)
524536

525537
url = self._api("/namespaces/{}/pods", namespace)
526538
response = self.session.post(url, json=template)
@@ -564,7 +576,7 @@ def run(self, namespace, name, image, entrypoint, command, **kwargs):
564576

565577
return 0, data
566578

567-
def _set_environment(self, data, **kwargs):
579+
def _set_environment(self, data, namespace, **kwargs):
568580
app_type = kwargs.get('app_type')
569581
mem = kwargs.get('memory', {}).get(app_type)
570582
cpu = kwargs.get('cpu', {}).get(app_type)
@@ -575,10 +587,30 @@ def _set_environment(self, data, **kwargs):
575587
data['env'] = []
576588

577589
if env:
578-
for key, value in env.items():
590+
# env vars are stored in secrets and mapped to env in k8s
591+
try:
592+
# secrets use dns labels for keys, map those properly here
593+
secrets_env = {}
594+
for key, value in env.items():
595+
secrets_env[key.lower().replace('_', '-')] = str(value)
596+
597+
secret_name = "{}-{}-env".format(namespace, kwargs.get('version'))
598+
self._get_secret(namespace, secret_name)
599+
except KubeHTTPException:
600+
self._create_secret(namespace, secret_name, secrets_env)
601+
else:
602+
self._update_secret(namespace, secret_name, secrets_env)
603+
604+
for key in env.keys():
579605
data["env"].append({
580606
"name": key,
581-
"value": str(value)
607+
"valueFrom": {
608+
"secretKeyRef": {
609+
"name": secret_name,
610+
# k8s doesn't allow _ so translate to -, see above
611+
"key": key.lower().replace('_', '-')
612+
}
613+
}
582614
})
583615

584616
# Inject debugging if workflow is in debug mode
@@ -906,7 +938,7 @@ def _create_rc(self, namespace, name, image, command, **kwargs): # noqa
906938
container = template["spec"]["template"]["spec"]["containers"][0]
907939
container['args'] = args
908940

909-
self._set_environment(container, **kwargs)
941+
self._set_environment(container, namespace, **kwargs)
910942

911943
# add in healtchecks
912944
if kwargs.get('healthcheck'):
@@ -1047,7 +1079,26 @@ def _create_secret(self, namespace, name, data):
10471079
url = self._api("/namespaces/{}/secrets", namespace)
10481080
response = self.session.post(url, json=template)
10491081
if unhealthy(response.status_code):
1050-
error(response, 'failed to create secret "{}" in Namespace "{}"', name, namespace)
1082+
error(response, 'failed to create Secret "{}" in Namespace "{}"', name, namespace)
1083+
1084+
return response
1085+
1086+
def _update_secret(self, namespace, name, data):
1087+
template = json.loads(string.Template(SECRET_TEMPLATE).substitute({
1088+
"version": self.apiversion,
1089+
"id": namespace,
1090+
"name": name
1091+
}))
1092+
1093+
for key, value in data.items():
1094+
value = value if isinstance(value, bytes) else bytes(value, 'UTF-8')
1095+
item = base64.b64encode(value).decode()
1096+
template["data"].update({key: item})
1097+
1098+
url = self._api("/namespaces/{}/secrets/{}", namespace, name)
1099+
response = self.session.put(url, json=template)
1100+
if unhealthy(response.status_code):
1101+
error(response, 'failed to update Secret "{}" in Namespace "{}"', name, namespace)
10511102

10521103
return response
10531104

0 commit comments

Comments
 (0)