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
101 changes: 101 additions & 0 deletions charts/openbot/templates/networkpolicy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,104 @@ spec:
{{ toYaml . | indent 4 }}
{{- end }}
{{- end }}

{{- if and .Values.networkPolicy.enabled (eq .Values.computers.mode "sandbox") .Values.computers.sandbox.culler.enabled }}
---
{{- $culler := "culler" -}}
{{/* A pod no policy selects keeps the cluster default, so this one was the release's only unfenced one. */}}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "openbot.componentName" (dict "root" . "component" $culler) }}
labels:
{{ include "openbot.componentLabels" (dict "root" . "component" $culler) | indent 4 }}
spec:
podSelector:
matchLabels:
{{ include "openbot.componentSelectorLabels" (dict "root" . "component" $culler) | indent 6 }}
policyTypes:
- Ingress
- Egress
# No ingress rules, which under an Ingress policyType denies all of it.
ingress: []
egress:
# DNS, or the database hostname does not resolve and the sweep reads as the database being down.
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
{{- if .Values.postgresql.enabled }}
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: postgresql
ports:
- port: 5432
protocol: TCP
{{- end }}
{{- /* Empty kubernetesApiCidr permits these ports to any address, as the server's rule above does. */}}
- {{- with .Values.networkPolicy.kubernetesApiCidr }}
to:
- ipBlock:
cidr: {{ . }}
{{- end }}
ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
{{- /* An external database, which extraEgress already had to name; cullerExtraEgress narrows it. */}}
{{- with (default .Values.networkPolicy.extraEgress .Values.networkPolicy.cullerExtraEgress) }}
{{ toYaml . | indent 4 }}
{{- end }}
{{- end }}

{{- if and .Values.networkPolicy.enabled .Values.routines.enabled }}
---
{{- $routines := "routines" -}}
{{/* Same reason as the culler; this pod holds no token and reaches only the database and the API server. */}}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "openbot.componentName" (dict "root" . "component" $routines) }}
labels:
{{ include "openbot.componentLabels" (dict "root" . "component" $routines) | indent 4 }}
spec:
podSelector:
matchLabels:
{{ include "openbot.componentSelectorLabels" (dict "root" . "component" $routines) | indent 6 }}
policyTypes:
- Ingress
- Egress
# No ingress rules, which under an Ingress policyType denies all of it.
ingress: []
egress:
# DNS, or neither the database nor the server resolves and the sweep reads as both being down.
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
{{- if .Values.postgresql.enabled }}
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: postgresql
ports:
- port: 5432
protocol: TCP
{{- end }}
{{- /* The API server, where SERVER_INTERNAL_URL points and the only thing this pod calls. */}}
- to:
- podSelector:
matchLabels:
{{ include "openbot.componentSelectorLabels" (dict "root" . "component" "server") | indent 14 }}
ports:
- port: {{ .Values.server.service.port }}
protocol: TCP
{{- /* An external database, for the reason given on the culler's policy above. */}}
{{- with (default .Values.networkPolicy.extraEgress .Values.networkPolicy.routinesExtraEgress) }}
{{ toYaml . | indent 4 }}
{{- end }}
{{- end }}
5 changes: 5 additions & 0 deletions charts/openbot/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,11 @@ networkPolicy:
# Where a Bot's computer may reach beyond the public internet. A deployment whose Bots must reach
# an internal site adds it here, one address at a time, rather than reopening the private ranges.
computerExtraEgress: []
# Extra egress for the culler. Empty falls back to `extraEgress`, where an external database is
# already named; set it to give the culler a narrower list.
cullerExtraEgress: []
# The same, for the routines sweep.
routinesExtraEgress: []

podSecurityContext:
runAsNonRoot: false
Expand Down
10 changes: 10 additions & 0 deletions scripts/check-new-values-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,21 @@ for (const { path, component, field } of fieldFallbacks) {
/*
* Found by its component label rather than by name, because a name is the release name plus a
* suffix and this check would then be pinned to both.
*
* Narrowed to pod-carrying kinds: a NetworkPolicy naming the same component carries the label too.
*/
const WORKLOAD_KINDS = new Set([
"CronJob",
"DaemonSet",
"Deployment",
"Job",
"StatefulSet",
]);
const carriers = parseAllDocuments(attempt.out)
.map((document) => document.toJS() as unknown)
.filter(
(resource) =>
WORKLOAD_KINDS.has(String(valueAt(resource, ["kind"]))) &&
valueAt(resource, [
"metadata",
"labels",
Expand Down
35 changes: 35 additions & 0 deletions scripts/check-rendered-chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,41 @@ if (serverPolicy) {
}
}

// A pod no policy selects keeps the cluster default, so on a release that has policies it is the
// only unfenced one. Asked of rendered objects, because the question is which pods came out.
const policyComponents = new Set(
documents
.filter((document) => /^kind:\s*NetworkPolicy\s*$/m.test(document))
.flatMap((document) => {
const selector = document.split(/^\s{2}podSelector:\s*$/m)[1] ?? "";
const found = selector.match(
/app\.kubernetes\.io\/component:\s*([\w-]+)/,
);
return found?.[1] ? [found[1]] : [];
}),
);
if (policyComponents.size > 0) {
const workloads = documents.filter((document) =>
/^kind:\s*(Deployment|StatefulSet|CronJob|Job|DaemonSet)\s*$/m.test(
document,
),
);
for (const workload of workloads) {
const component = workload.match(
/app\.kubernetes\.io\/component:\s*([\w-]+)/,
)?.[1];
const name = workload.match(/^\s{2}name:\s*(\S+)/m)?.[1] ?? "a workload";
if (!component) continue;
// The migrations Job runs once at install and is torn down; it is not a standing surface.
if (component === "migrations") continue;
if (!policyComponents.has(component)) {
problems.push(
`This release has NetworkPolicies but none selects ${name} (component: ${component}), so it is the one pod left unfenced while everything around it is restricted. Give it a policy or say in the chart why it needs none.`,
);
}
}
}

if (problems.length > 0) {
for (const problem of problems) console.error(`::error::${problem}`);
process.exit(1);
Expand Down