Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ apiVersion: v2
name: pgdog-control
description: PgDog Control
type: application
version: 0.4.0
version: 0.4.1
appVersion: "main-ent"
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,23 @@ In the above example, the dashboard can see workloads in every namespace, but it
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `control.rbac.create` | Render the ServiceAccount and the RBAC bindings. When `false`, no RBAC is rendered and the pod runs without a mounted API token. The Kubernetes views in the dashboard will be empty (bool, default `true`). |
| `control.rbac.serviceAccountName` | Override the generated ServiceAccount name. Empty falls back to `<release>-control` (string, default `""`). |
| `control.rbac.existingServiceAccount` | Run the pod as this existing ServiceAccount. Requires `create: false`; see [Disabling RBAC](#disabling-rbac) (string, default `""`). |
| `control.rbac.writeNamespaces` | Namespaces where the control plane is allowed to manage PgDog workloads. Each entry produces one Role + RoleBinding pair. Empty means the install is read-only everywhere (list, default `[]`). |

### Disabling RBAC

If your cluster manages RBAC out-of-band (a platform team's controller, GitOps, an admission policy), set `control.rbac.create: false`. The chart then renders no ServiceAccount, no ClusterRole/Binding, and no Role/Bindings, and the deployment runs the pod with `automountServiceAccountToken: false`. The dashboard still serves the UI, but every Kubernetes-backed view will be empty until you bind an externally-managed ServiceAccount with equivalent permissions to the pod yourself.
If your cluster manages RBAC out-of-band (a platform team's controller, GitOps, an admission policy), set `control.rbac.create: false`. The chart then renders no ServiceAccount, no ClusterRole/Binding, and no Role/Bindings, and the deployment runs the pod with `automountServiceAccountToken: false`. The dashboard still serves the UI, but every Kubernetes-backed view will be empty.

To run the pod as a ServiceAccount you manage, with RBAC bound out of band, an IRSA role, or both, name it in `control.rbac.existingServiceAccount`:

```yaml
control:
rbac:
create: false
existingServiceAccount: pgdog-control
```

The ServiceAccount must exist before `helm install`. The chart leaves `automountServiceAccountToken` unset on the pod, so the ServiceAccount's own setting decides whether the pod gets a Kubernetes API token: keep it enabled for the Kubernetes views, or set it to `false` when the pod only needs IRSA. `control.aws.roleArn` is rejected in this mode; annotate the ServiceAccount with `eks.amazonaws.com/role-arn` yourself.

## NetworkPolicy

Expand Down Expand Up @@ -365,7 +377,7 @@ This needs three things, only one of which is in the chart:

### Trust policy

The role must trust the cluster's OIDC provider and scope the trust to the control plane's ServiceAccount. That subject is `system:serviceaccount:<release-namespace>:<release>-control`, or whatever `control.rbac.serviceAccountName` is if you overrode it. Confirm the exact subject after `helm install` with:
The role must trust the cluster's OIDC provider and scope the trust to the control plane's ServiceAccount. That subject is `system:serviceaccount:<release-namespace>:<release>-control`, or whatever `control.rbac.serviceAccountName` or `control.rbac.existingServiceAccount` names. Confirm the exact subject after `helm install` with:

```sh
kubectl -n <release-namespace> get sa \
Expand Down
8 changes: 8 additions & 0 deletions templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,15 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.control.rbac.existingServiceAccount }}
{{- if .Values.control.rbac.create }}
{{- fail "control.rbac.existingServiceAccount runs the pod without the chart's RBAC; set control.rbac.create=false" }}
{{- end }}
{{- if .Values.control.aws.roleArn }}
{{- fail "control.aws.roleArn annotates the chart's ServiceAccount; annotate control.rbac.existingServiceAccount instead" }}
{{- end }}
serviceAccountName: {{ .Values.control.rbac.existingServiceAccount }}
{{- else if .Values.control.rbac.create }}
serviceAccountName: {{ include "pgdog-control.control.serviceAccountName" . }}
automountServiceAccountToken: true
{{- else }}
Expand Down
27 changes: 27 additions & 0 deletions test/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,32 @@ grep -q '^ query_plans_limit = 75$' <<< "$full_render"
grep -q '^ slow_queries_threshold = 2500$' <<< "$full_render"
grep -q '^ slow_queries = true$' <<< "$full_render"

echo ""
echo "==> Verifying an existing ServiceAccount without RBAC..."
control_sa() {
helm template test-release "$CHART_DIR" "$@" \
| yq -o=json -I=0 'select(.kind == "Deployment" and .metadata.name == "test-release-control") | .spec.template.spec | {"sa": .serviceAccountName, "automount": .automountServiceAccountToken}'
}
sa_render=$(helm template test-release "$CHART_DIR" -f "$TEST_DIR/values-existing-service-account.yaml")
if [ "$(control_sa -f "$TEST_DIR/values-existing-service-account.yaml")" != '{"sa":"pgdog-control-aws","automount":null}' ]; then
echo "control pod does not run as the existing ServiceAccount, or overrides its token setting" >&2
exit 1
fi
if grep -qE '^kind: (ServiceAccount|ClusterRole|ClusterRoleBinding)$' <<< "$sa_render"; then
echo "RBAC resources rendered while control.rbac.create=false" >&2
exit 1
fi
# Opt-in only: without existingServiceAccount, create=false keeps the default SA and ignores roleArn.
if [ "$(control_sa --set control.rbac.create=false --set control.rbac.serviceAccountName=foo --set control.aws.roleArn=arn:aws:iam::123456789012:role/x)" != '{"sa":null,"automount":false}' ]; then
echo "create=false without existingServiceAccount changed the control pod's ServiceAccount" >&2
exit 1
fi
for conflict in "control.rbac.create=true" "control.aws.roleArn=arn:aws:iam::123456789012:role/x"; do
if helm template test-release "$CHART_DIR" -f "$TEST_DIR/values-existing-service-account.yaml" --set "$conflict" >/dev/null 2>&1; then
echo "existingServiceAccount rendered with $conflict" >&2
exit 1
fi
done

echo ""
echo "==> All chart tests passed!"
5 changes: 5 additions & 0 deletions test/values-existing-service-account.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Run as an existing ServiceAccount (e.g. IRSA) without the chart's RBAC.
control:
rbac:
create: false
existingServiceAccount: pgdog-control-aws
6 changes: 6 additions & 0 deletions values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ control:
# Override the ServiceAccount name. When empty, falls back to
# `<release>-control`.
serviceAccountName: ""
# Run the control pod as this existing ServiceAccount instead, e.g. one
# with RBAC managed out of band or annotated for EKS IRSA. Requires
# create: false; the ServiceAccount must exist before install and carry
# its own role annotation (roleArn is not used). Its own
# automountServiceAccountToken decides whether the pod gets an API token.
existingServiceAccount: ""
# Namespaces where the control plane is allowed to create/update/patch/delete
# workloads (deployments, statefulsets, configmaps, secrets, services).
# A Role + RoleBinding is generated in each listed namespace. Empty
Expand Down
Loading