2026-03-30 17:44:11 -07:00
|
|
|
# Compensating Controls
|
|
|
|
|
#
|
|
|
|
|
# Documents controls that mitigate risks from suppressed or accepted security
|
|
|
|
|
# findings. Referenced by security tools (Prowler mutelist, Kingfisher config,
|
|
|
|
|
# etc.) via "CC: <id>" in finding descriptions or suppression notes.
|
|
|
|
|
#
|
|
|
|
|
# Used by `mise run review-compensating-controls` to surface stale controls.
|
|
|
|
|
#
|
|
|
|
|
# Fields:
|
|
|
|
|
# id - kebab-case unique identifier, referenced from tool configs
|
|
|
|
|
# description - what the control actually does to mitigate risk
|
|
|
|
|
# created - date (YYYY-MM-DD) the control was documented
|
|
|
|
|
# last-reviewed - date (YYYY-MM-DD) or null
|
|
|
|
|
# notes - optional context
|
|
|
|
|
|
|
|
|
|
controls:
|
|
|
|
|
- id: single-user-cluster
|
|
|
|
|
description: >-
|
|
|
|
|
Only the cluster operator (eblume) has kubectl access. No untrusted
|
|
|
|
|
users can create pods, access cached images, or bind RBAC roles.
|
|
|
|
|
created: 2026-03-30
|
2026-04-01 22:01:57 -07:00
|
|
|
last-reviewed: 2026-04-01
|
2026-03-30 17:44:11 -07:00
|
|
|
notes: >-
|
|
|
|
|
Verify by checking kubeconfig distribution and Tailscale ACLs.
|
|
|
|
|
If additional users gain cluster access, re-evaluate all findings
|
|
|
|
|
muted under this control.
|
|
|
|
|
|
|
|
|
|
- id: tailscale-network-isolation
|
|
|
|
|
description: >-
|
|
|
|
|
Cluster is not internet-exposed. All access requires Tailscale
|
|
|
|
|
identity with ACL enforcement. Profiling endpoints, debug ports,
|
|
|
|
|
and control-plane APIs are unreachable from the public internet.
|
|
|
|
|
created: 2026-03-30
|
2026-04-06 10:35:13 -07:00
|
|
|
last-reviewed: 2026-04-06
|
2026-03-30 17:44:11 -07:00
|
|
|
notes: >-
|
|
|
|
|
Verify with 'tailscale serve status --json' on indri and review
|
|
|
|
|
Tailscale ACLs in pulumi/tailscale/. Only tag:flyio-target services
|
|
|
|
|
are publicly routable.
|
|
|
|
|
|
|
|
|
|
- id: local-registry
|
|
|
|
|
description: >-
|
2026-04-12 09:59:37 -07:00
|
|
|
Operator-built services use a private zot registry
|
|
|
|
|
(registry.ops.eblu.me) for supply-chain control. Remaining
|
|
|
|
|
images are pulled from public registries without stored
|
|
|
|
|
credentials. No shared registry secrets are cached on cluster
|
|
|
|
|
nodes.
|
2026-03-30 17:44:11 -07:00
|
|
|
created: 2026-03-30
|
2026-04-12 09:59:37 -07:00
|
|
|
last-reviewed: 2026-04-12
|
2026-03-30 17:44:11 -07:00
|
|
|
notes: >-
|
|
|
|
|
Verify by checking image prefixes in kustomization.yaml files.
|
2026-04-12 09:59:37 -07:00
|
|
|
Known external-image categories: (1) upstream apps not yet
|
|
|
|
|
mirrored — immich, ollama, frigate, frigate-notify, valkey;
|
|
|
|
|
(2) infrastructure components — tailscale operator/proxy,
|
|
|
|
|
external-secrets, 1password-connect, forgejo-runner, docker
|
|
|
|
|
DinD, nvidia-device-plugin; (3) utility base images — busybox,
|
|
|
|
|
alpine (grafana init containers). Track upstream versions in
|
|
|
|
|
service-versions.yaml. Goal is to progressively mirror these
|
|
|
|
|
into zot.
|
2026-03-30 17:44:11 -07:00
|
|
|
|
|
|
|
|
- id: sso-gated-admin-tools
|
|
|
|
|
description: >-
|
2026-04-14 13:07:52 -07:00
|
|
|
ArgoCD requires SSO authentication via Authentik OIDC. Wildcard
|
|
|
|
|
RBAC roles are mitigated by requiring authenticated identity
|
|
|
|
|
before any API access.
|
2026-03-30 17:44:11 -07:00
|
|
|
created: 2026-03-30
|
2026-04-14 13:07:52 -07:00
|
|
|
last-reviewed: 2026-04-14
|
2026-03-30 17:44:11 -07:00
|
|
|
notes: >-
|
2026-04-14 13:07:52 -07:00
|
|
|
Verify Authentik OIDC provider config for ArgoCD and that
|
|
|
|
|
anonymous access is disabled. Check ArgoCD --auth-token isn't
|
|
|
|
|
leaked. The workflow-bot API key account is scoped to sync/get
|
|
|
|
|
only.
|
2026-03-30 17:44:11 -07:00
|
|
|
|
|
|
|
|
- id: operator-managed-pods
|
|
|
|
|
description: >-
|
|
|
|
|
Tailscale operator manages proxy pod specs (ts-*, ingress-*,
|
|
|
|
|
operator-*, nameserver-*). Pod security settings are set by the
|
|
|
|
|
operator, not user manifests. Operator is tracked in
|
|
|
|
|
service-versions.yaml and regularly updated.
|
|
|
|
|
created: 2026-03-30
|
|
|
|
|
last-reviewed: 2026-03-30
|
|
|
|
|
notes: >-
|
|
|
|
|
Verify operator version is current via 'mise run service-review'.
|
|
|
|
|
Check Tailscale changelog for security fixes. If operator adds
|
|
|
|
|
seccomp support, remove these mutes.
|
|
|
|
|
|
|
|
|
|
- id: ephemeral-privileged-jobs
|
|
|
|
|
description: >-
|
|
|
|
|
Prowler CIS scanner runs as a CronJob with 7-day TTL
|
|
|
|
|
auto-deletion, not as a persistent privileged workload. hostPID
|
|
|
|
|
exposure is time-bounded to scan duration (~20s).
|
|
|
|
|
created: 2026-03-30
|
|
|
|
|
last-reviewed: 2026-03-30
|
|
|
|
|
notes: >-
|
|
|
|
|
Verify TTL is set in cronjob.yaml. Check that no persistent
|
|
|
|
|
pods run with hostPID.
|
|
|
|
|
|
|
|
|
|
- id: trusted-ci-only
|
|
|
|
|
description: >-
|
|
|
|
|
Forgejo runner only executes workflows from repos on the private
|
|
|
|
|
forge (forge.ops.eblu.me). No external or untrusted repos can
|
|
|
|
|
trigger privileged CI jobs.
|
|
|
|
|
created: 2026-03-30
|
|
|
|
|
last-reviewed: 2026-03-30
|
|
|
|
|
notes: >-
|
|
|
|
|
Verify runner registration is limited to the forge instance.
|
|
|
|
|
Check Forgejo runner config for repo allow-lists.
|
|
|
|
|
|
|
|
|
|
- id: init-container-isolation
|
|
|
|
|
description: >-
|
|
|
|
|
Root privileges and added capabilities (CHOWN) are limited to
|
|
|
|
|
init containers that run once at pod startup. All runtime
|
|
|
|
|
containers run as non-root (UID 472) with all capabilities
|
|
|
|
|
dropped.
|
|
|
|
|
created: 2026-03-30
|
|
|
|
|
last-reviewed: 2026-03-30
|
|
|
|
|
notes: >-
|
|
|
|
|
Verify by inspecting grafana deployment.yaml securityContext
|
|
|
|
|
for both init and runtime containers. If fsGroup alone can
|
|
|
|
|
handle PVC ownership, remove init-chown-data and this control.
|
|
|
|
|
|
2026-04-14 13:00:44 -07:00
|
|
|
- id: node-config-automated-verification
|
|
|
|
|
description: >-
|
|
|
|
|
Prowler reports certain node-level checks as MANUAL because it runs
|
|
|
|
|
inside a pod and cannot evaluate kubelet file permissions, kubelet
|
|
|
|
|
config arguments, etcd CA separation, or cluster-admin RBAC bindings.
|
|
|
|
|
The review-compliance-reports script SSHes into the minikube node
|
|
|
|
|
weekly and programmatically verifies each condition, failing loudly
|
|
|
|
|
if any check deviates from expected values.
|
|
|
|
|
created: 2026-04-14
|
|
|
|
|
last-reviewed: 2026-04-14
|
|
|
|
|
notes: >-
|
|
|
|
|
Verification runs as part of 'mise run review-compliance-reports'.
|
|
|
|
|
If minikube node is unreachable, all checks report as FAIL. If new
|
|
|
|
|
MANUAL findings appear in Prowler, add corresponding verification
|
|
|
|
|
logic to the script and update the mutelist.
|
|
|
|
|
|
2026-03-30 17:44:11 -07:00
|
|
|
- id: observability-stack-audit
|
|
|
|
|
description: >-
|
|
|
|
|
Alloy collects pod logs and ships them to Loki, providing an
|
|
|
|
|
audit trail for cluster activity. Compensates for missing
|
|
|
|
|
apiserver audit logging which minikube does not configure.
|
|
|
|
|
created: 2026-03-30
|
|
|
|
|
last-reviewed: 2026-03-30
|
|
|
|
|
notes: >-
|
|
|
|
|
Verify Alloy DaemonSet is running and Loki is receiving logs.
|
|
|
|
|
Note this is weaker than native apiserver audit logs — it
|
|
|
|
|
captures pod stdout/stderr, not API request-level auditing.
|
|
|
|
|
Consider enabling minikube audit logging if supported.
|