Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion api/azimuth/cluster_engine/drivers/crd/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ def get_k8s_client(ctx: dto.Context, ensure_namespace: bool = False):
client = ekconfig.sync_client()
client.default_namespace = utils.get_namespace(client, ctx.tenancy)
if ensure_namespace:
utils.ensure_namespace(client, client.default_namespace, ctx.tenancy)
try:
utils.ensure_namespace(client, client.default_namespace, ctx.tenancy)
except easykube.ApiError as exc:
raise errors.CommunicationError(str(exc)) from exc
except RuntimeError as exc:
raise errors.CommunicationError(str(exc)) from exc
return client


Expand Down
83 changes: 83 additions & 0 deletions api/azimuth/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os
import re

import easykube
Expand Down Expand Up @@ -111,6 +112,87 @@ def get_namespace(ekclient, tenancy: dto.Tenancy) -> str:
raise NamespaceOwnershipError(expected_namespace, tenancy_id, owner_id)


_SECRETS_ROLE_NAME = "azimuth-api-secrets"


def ensure_secrets_rbac(ekclient, namespace: str):
"""
Ensures the API service account has the necessary secrets permissions in
the given namespace via a namespaced Role + RoleBinding.

The service account identity is read from env vars injected by the Helm
chart via the Kubernetes downward API (AZIMUTH_SA_NAME / AZIMUTH_SA_NAMESPACE).
If either variable is absent the function is a no-op so that local test
runs outside a cluster are not broken.
"""
sa_name = os.environ.get("AZIMUTH_SA_NAME")
sa_namespace = os.environ.get("AZIMUTH_SA_NAMESPACE")
if not sa_name or not sa_namespace:
logger.warning(
"AZIMUTH_SA_NAME or AZIMUTH_SA_NAMESPACE not set; "
"skipping per-namespace secrets RBAC in '%s'",
namespace,
)
return

rbac = ekclient.api("rbac.authorization.k8s.io/v1")

try:
rbac.resource("roles").create_or_patch(
_SECRETS_ROLE_NAME,
{
"metadata": {"name": _SECRETS_ROLE_NAME},
"rules": [
{
"apiGroups": [""],
"resources": ["secrets"],
"verbs": ["list", "get", "create", "update", "patch", "delete"],
}
],
},
namespace=namespace,
)
except easykube.ApiError as exc:
if exc.response.status_code == 403:
raise RuntimeError(
f"Permission denied creating secrets Role '{_SECRETS_ROLE_NAME}' "
f"in namespace '{namespace}': the API ClusterRole must include "
f"'escalate' on roles (Kubernetes escalation-prevention). "
f"Kubernetes said: {exc}"
) from exc
raise

try:
rbac.resource("rolebindings").create_or_patch(
_SECRETS_ROLE_NAME,
{
"metadata": {"name": _SECRETS_ROLE_NAME},
"subjects": [
{
"kind": "ServiceAccount",
"name": sa_name,
"namespace": sa_namespace,
}
],
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "Role",
"name": _SECRETS_ROLE_NAME,
},
},
namespace=namespace,
)
except easykube.ApiError as exc:
if exc.response.status_code == 403:
raise RuntimeError(
f"Permission denied creating secrets RoleBinding"
f" '{_SECRETS_ROLE_NAME}' in namespace '{namespace}':"
f" the API ClusterRole must include 'bind' on roles"
f" (Kubernetes bind-prevention). Kubernetes said: {exc}"
) from exc
raise


def ensure_namespace(ekclient, namespace: str, tenancy: dto.Tenancy):
"""
Ensures that the specified namespace exists and is labelled correctly for
Expand All @@ -130,3 +212,4 @@ def ensure_namespace(ekclient, namespace: str, tenancy: dto.Tenancy):
},
},
)
ensure_secrets_rbac(ekclient, namespace)
16 changes: 7 additions & 9 deletions api/azimuth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1426,12 +1426,11 @@ def cluster_service(request, tenant, cluster, service):
service_fqdn = None
service_label = None
try:
if cloud_settings.CLUSTER_ENGINE:
with request.auth.scoped_session(tenant) as session:
with cloud_settings.CLUSTER_ENGINE.create_manager(
session
) as cluster_manager:
cluster = cluster_manager.find_cluster(cluster)
with request.auth.scoped_session(tenant) as session:
with cloud_settings.CLUSTER_ENGINE.create_manager(
session
) as cluster_manager:
cluster = cluster_manager.find_cluster(cluster)
service_obj = next(s for s in cluster.services if s.name == service)
service_fqdn = service_obj.fqdn
service_label = service_obj.label
Expand Down Expand Up @@ -1834,9 +1833,8 @@ def kubernetes_cluster_service(request, tenant, cluster, service):
service_fqdn = None
service_label = None
try:
if cloud_settings.CLUSTER_API_PROVIDER:
with cloud_settings.CLUSTER_API_PROVIDER.session(session) as capi_session:
cluster = capi_session.find_cluster(cluster)
with cloud_settings.CLUSTER_API_PROVIDER.session(session) as capi_session:
cluster = capi_session.find_cluster(cluster)
service_obj = next(s for s in cluster.services if s.name == service)
service_fqdn = service_obj.fqdn
service_label = service_obj.label
Expand Down
4 changes: 0 additions & 4 deletions chart/files/api/settings/07-disable-kubernetes.yaml

This file was deleted.

9 changes: 9 additions & 0 deletions chart/files/api/settings/07-feature-flags.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{- if or (not .Values.tags.kubernetes) (not .Values.tags.clusters) -}}
AZIMUTH:
{{- if not .Values.tags.kubernetes }}
CLUSTER_API_PROVIDER: null
{{- end }}
{{- if not .Values.tags.clusters }}
CLUSTER_ENGINE: null
{{- end }}
{{- end }}
55 changes: 0 additions & 55 deletions chart/files/api/settings/08-awx.yaml

This file was deleted.

8 changes: 0 additions & 8 deletions chart/files/api/settings/09-awx-passwords.py

This file was deleted.

4 changes: 0 additions & 4 deletions chart/files/api/settings/10-disable-caas.yaml

This file was deleted.

10 changes: 5 additions & 5 deletions chart/templates/api/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,16 @@ rules:
- create
- patch
- apiGroups:
- ""
- rbac.authorization.k8s.io
resources:
- secrets
- roles
- rolebindings
verbs:
- list
- get
- create
- update
- patch
- delete
- bind
- escalate
- apiGroups:
- caas.azimuth.stackhpc.com
resources:
Expand Down
29 changes: 6 additions & 23 deletions chart/templates/api/deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{{- $component := "api" -}}
{{- $values := get .Values $component -}}
{{- $clusterEngine := .Values.clusterEngine -}}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -50,30 +49,15 @@ spec:
securityContext: {{ toYaml $values.securityContext | nindent 12 }}
image: {{ printf "%s:%s" $values.image.repository (default .Chart.AppVersion $values.image.tag) }}
imagePullPolicy: {{ $values.image.pullPolicy }}
{{-
if or
$values.monitoring.enabled
(and
(eq .Values.authentication.type "oidc")
(hasPrefix "http://" .Values.authentication.oidc.issuerUrl)
)
(and .Values.tags.clusters (eq $clusterEngine.type "awx"))
}}
env:
{{- if and .Values.tags.clusters (eq $clusterEngine.type "awx") }}
- name: AWX_PASSWORD
- name: AZIMUTH_SA_NAME
valueFrom:
secretKeyRef:
name: {{ required "clusterEngine.awx.passwordSecretName is required" $clusterEngine.awx.passwordSecretName | quote }}
key: password
{{- with $clusterEngine.awx.adminPasswordSecretName }}
- name: AWX_ADMIN_PASSWORD
fieldRef:
fieldPath: spec.serviceAccountName
- name: AZIMUTH_SA_NAMESPACE
valueFrom:
secretKeyRef:
name: {{ quote . }}
key: password
{{- end }}
{{- end }}
fieldRef:
fieldPath: metadata.namespace
{{- if $values.monitoring.enabled }}
- name: GUNICORN_STATSD_HOST
value: "localhost:9125"
Expand All @@ -86,7 +70,6 @@ spec:
- name: OAUTHLIB_INSECURE_TRANSPORT
value: "1"
{{- end }}
{{- end }}
ports:
- name: http
containerPort: 8080
Expand Down
12 changes: 3 additions & 9 deletions chart/templates/api/settings.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if not (has .Values.clusterEngine.type (list "crd" "awx")) -}}
{{- if ne .Values.clusterEngine.type "crd" -}}
{{- fail (printf "Unsupported cluster engine '%s'" .Values.clusterEngine.type) }}
{{- end }}

Expand All @@ -23,14 +23,8 @@ data:
{{- tpl (.Files.Get "files/api/settings/05-apps.yaml") . | b64enc | nindent 4 }}
06-ssh-key-store.yaml: |
{{- tpl (.Files.Get "files/api/settings/06-ssh-key-store.yaml") . | b64enc | nindent 4 }}
07-disable-kubernetes.yaml: |
{{- tpl (.Files.Get "files/api/settings/07-disable-kubernetes.yaml") . | b64enc | nindent 4 }}
08-awx.yaml: |
{{- tpl (.Files.Get "files/api/settings/08-awx.yaml") . | b64enc | nindent 4 }}
09-awx-passwords.py: |
{{- tpl (.Files.Get "files/api/settings/09-awx-passwords.py") . | b64enc | nindent 4 }}
10-disable-caas.yaml: |
{{- tpl (.Files.Get "files/api/settings/10-disable-caas.yaml") . | b64enc | nindent 4 }}
07-feature-flags.yaml: |
{{- tpl (.Files.Get "files/api/settings/07-feature-flags.yaml") . | b64enc | nindent 4 }}
11-scheduling.yaml: |
{{- tpl (.Files.Get "files/api/settings/11-scheduling.yaml") . | b64enc | nindent 4 }}
12-apps-provider.yaml: |
Expand Down
31 changes: 17 additions & 14 deletions chart/tests/__snapshot__/snapshot_test.yaml.snap
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,16 @@ templated manifests should match snapshot:
- create
- patch
- apiGroups:
- ""
- rbac.authorization.k8s.io
resources:
- secrets
- roles
- rolebindings
verbs:
- list
- get
- create
- update
- patch
- delete
- bind
- escalate
- apiGroups:
- caas.azimuth.stackhpc.com
resources:
Expand Down Expand Up @@ -158,7 +158,7 @@ templated manifests should match snapshot:
template:
metadata:
annotations:
azimuth.stackhpc.com/settings-checksum: ba9764bba470b2cacf65a4646a795dd5a62707a4879baac5749a8884af5dc0cd
azimuth.stackhpc.com/settings-checksum: 65ccb797a35210f0ae53ea1af4483f357627b78cc7c97b321de04fb78bdd6ed8
azimuth.stackhpc.com/theme-checksum: ec0f36322392deee39d80b7f77ecd634df60358857af9dc208077860c4e174ab
kubectl.kubernetes.io/default-container: api
labels:
Expand All @@ -167,7 +167,16 @@ templated manifests should match snapshot:
app.kubernetes.io/name: azimuth
spec:
containers:
- image: ghcr.io/azimuth-cloud/azimuth-api:master
- env:
- name: AZIMUTH_SA_NAME
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: AZIMUTH_SA_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: ghcr.io/azimuth-cloud/azimuth-api:master
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
Expand Down Expand Up @@ -261,13 +270,7 @@ templated manifests should match snapshot:
Cg==
06-ssh-key-store.yaml: |
QVpJTVVUSDoKICBTU0hfS0VZX1NUT1JFOgogICAgRkFDVE9SWTogYXppbXV0aC5rZXlzdG9yZS5wcm92aWRlci5Qcm92aWRlcktleVN0b3JlCg==
07-disable-kubernetes.yaml: |
Cg==
08-awx.yaml: |
Cg==
09-awx-passwords.py: |
Cg==
10-disable-caas.yaml: |
07-feature-flags.yaml: |
QVpJTVVUSDoKICBDTFVTVEVSX0VOR0lORTogbnVsbAo=
11-scheduling.yaml: |
QVpJTVVUSDoKICBTQ0hFRFVMSU5HOgogICAgRU5BQkxFRDogZmFsc2UK
Expand Down
Loading
Loading