Add identity and access audit
This commit is contained in:
parent
0777bed10c
commit
d2c959bebd
20
README.md
20
README.md
|
|
@ -776,6 +776,26 @@ Run the same validation locally before promoting:
|
||||||
./jeannie validate
|
./jeannie validate
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Identity and access audit
|
||||||
|
|
||||||
|
Run a read-only access inventory from the Debian server with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./jeannie access-audit
|
||||||
|
```
|
||||||
|
|
||||||
|
The audit reports configured SSH targets and key file modes, Git/Gitea remotes,
|
||||||
|
Tailscale ACL policy validation, current Kubernetes authorization, broad
|
||||||
|
cluster-admin bindings, automation service accounts, and Gitea runner status.
|
||||||
|
It does not create users, tokens, kubeconfigs, or keys. Use it to move toward
|
||||||
|
separate identities:
|
||||||
|
|
||||||
|
- admin kubeconfig only on the Debian control host
|
||||||
|
- read-only kubeconfig for dashboards and audits
|
||||||
|
- separate service accounts for automation
|
||||||
|
- explicit SSH inventory for Debian, RPi, Pimox, and OCI edge
|
||||||
|
- repo-managed Tailscale ACLs
|
||||||
|
|
||||||
The workflow only blocks automatic deploy for external Gitea service
|
The workflow only blocks automatic deploy for external Gitea service
|
||||||
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
||||||
`install_gitea_backup_timer`, `backup_gitea`, or `drill_gitea_restore`
|
`install_gitea_backup_timer`, `backup_gitea`, or `drill_gitea_restore`
|
||||||
|
|
|
||||||
|
|
@ -776,6 +776,26 @@ Run the same validation locally before promoting:
|
||||||
./{{ main_script }} validate
|
./{{ main_script }} validate
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Identity and access audit
|
||||||
|
|
||||||
|
Run a read-only access inventory from the Debian server with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./{{ main_script }} access-audit
|
||||||
|
```
|
||||||
|
|
||||||
|
The audit reports configured SSH targets and key file modes, Git/Gitea remotes,
|
||||||
|
Tailscale ACL policy validation, current Kubernetes authorization, broad
|
||||||
|
cluster-admin bindings, automation service accounts, and Gitea runner status.
|
||||||
|
It does not create users, tokens, kubeconfigs, or keys. Use it to move toward
|
||||||
|
separate identities:
|
||||||
|
|
||||||
|
- admin kubeconfig only on the Debian control host
|
||||||
|
- read-only kubeconfig for dashboards and audits
|
||||||
|
- separate service accounts for automation
|
||||||
|
- explicit SSH inventory for Debian, RPi, Pimox, and OCI edge
|
||||||
|
- repo-managed Tailscale ACLs
|
||||||
|
|
||||||
The workflow only blocks automatic deploy for external Gitea service
|
The workflow only blocks automatic deploy for external Gitea service
|
||||||
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
changes: files under `infra/gitea/`, or edits inside the `deploy_gitea`,
|
||||||
`install_gitea_backup_timer`, `backup_gitea`, or `drill_gitea_restore`
|
`install_gitea_backup_timer`, `backup_gitea`, or `drill_gitea_restore`
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,11 @@ deployment readiness, pod restarts, node pressure, disk pressure, Traefik
|
||||||
inventory, generated docs, Bash syntax, shellcheck, YAML parsing, OpenTofu
|
inventory, generated docs, Bash syntax, shellcheck, YAML parsing, OpenTofu
|
||||||
formatting, tailnet policy validation, and optional static security checks.
|
formatting, tailnet policy validation, and optional static security checks.
|
||||||
|
|
||||||
|
`access-audit`
|
||||||
|
: Run a read-only access inventory across SSH targets, Git/Gitea remotes,
|
||||||
|
Tailscale ACL validation, Kubernetes RBAC, automation service accounts, and the
|
||||||
|
Gitea runner.
|
||||||
|
|
||||||
`nuke`
|
`nuke`
|
||||||
: Destroy Kubernetes state and Pimox worker VMs. Requires
|
: Destroy Kubernetes state and Pimox worker VMs. Requires
|
||||||
`LAB_CONFIRM_NUKE=homelab`.
|
`LAB_CONFIRM_NUKE=homelab`.
|
||||||
|
|
|
||||||
10
jeannie
10
jeannie
|
|
@ -5943,6 +5943,11 @@ validate_homelab() {
|
||||||
"${REPO_ROOT}/scripts/validate-homelab"
|
"${REPO_ROOT}/scripts/validate-homelab"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
access_audit() {
|
||||||
|
require_debian_server "access-audit"
|
||||||
|
"${REPO_ROOT}/scripts/access-audit"
|
||||||
|
}
|
||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
up)
|
up)
|
||||||
up
|
up
|
||||||
|
|
@ -5965,6 +5970,9 @@ case "${1:-}" in
|
||||||
validate)
|
validate)
|
||||||
validate_homelab
|
validate_homelab
|
||||||
;;
|
;;
|
||||||
|
access-audit)
|
||||||
|
access_audit
|
||||||
|
;;
|
||||||
apps)
|
apps)
|
||||||
require_debian_server "apps"
|
require_debian_server "apps"
|
||||||
jeannie_log_start "apps"
|
jeannie_log_start "apps"
|
||||||
|
|
@ -6094,7 +6102,7 @@ case "${1:-}" in
|
||||||
echo "Log: ${JEANNIE_LOG_FILE}"
|
echo "Log: ${JEANNIE_LOG_FILE}"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|validate|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|security-attack-path|openwrt|nuke}"
|
echo "Usage: $0 {up|plan [all|provisioning|cluster|platform|apps|edge]|rebuild-cluster|stop-cluster|start-cluster|status|validate|access-audit|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|state-backup|fix-debian-docker-root|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|security-scan|security-prepare|security-zap|security-k8s|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-runtime|security-attack-path|openwrt|nuke}"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
KUBECONFIG="${KUBECONFIG:-${HOME}/.kube/config}"
|
||||||
|
|
||||||
|
section() {
|
||||||
|
printf '\n== %s ==\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
warn_file_mode() {
|
||||||
|
local path="$1"
|
||||||
|
local mode
|
||||||
|
|
||||||
|
if [[ ! -e "${path}" ]]; then
|
||||||
|
printf '%-34s missing - %s\n' "key file" "${path}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
mode="$(stat -c '%a' "${path}" 2>/dev/null || stat -f '%Lp' "${path}" 2>/dev/null || true)"
|
||||||
|
printf '%-34s %s mode=%s\n' "key file" "${path}" "${mode:-unknown}"
|
||||||
|
case "${mode}" in
|
||||||
|
600 | 400)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf ' warn: private SSH keys should normally be 0600 or 0400\n'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_inventory() {
|
||||||
|
section "SSH Inventory"
|
||||||
|
printf '%-18s %s@%s key=%s\n' "Debian" "${USER:-jv}" "${LAB_DEBIAN_HOST:-${LAB_GITEA_HOST:-192.168.100.73}}" "${LAB_DEBIAN_SSH_KEY_PATH:-${HOME}/.ssh/id_ed25519}"
|
||||||
|
printf '%-18s %s@%s key=%s\n' "RPi4" "${LAB_RPI_USER:-${LAB_RASPBERRY_USER:-jv}}" "${LAB_RPI_HOST:-${LAB_RASPBERRY_HOST:-192.168.100.89}}" "${LAB_RPI_SSH_KEY_PATH:-${LAB_RASPBERRY_SSH_KEY_PATH:-${HOME}/.ssh/id_ed25519}}"
|
||||||
|
printf '%-18s %s@%s key=%s\n' "Pimox" "${LAB_PIMOX_USER:-${TF_VAR_pimox_user:-jv}}" "${LAB_PIMOX_HOST:-${TF_VAR_pimox_host:-192.168.100.80}}" "${LAB_PIMOX_SSH_KEY_PATH:-${TF_VAR_pimox_ssh_key_path:-${HOME}/.ssh/id_ed25519}}"
|
||||||
|
printf '%-18s %s@%s\n' "OCI edge" "${LAB_EDGE_USER:-ubuntu}" "${LAB_EDGE_HOST:-132.145.170.74}"
|
||||||
|
|
||||||
|
warn_file_mode "${LAB_DEBIAN_SSH_KEY_PATH:-${HOME}/.ssh/id_ed25519}"
|
||||||
|
}
|
||||||
|
|
||||||
|
git_access() {
|
||||||
|
section "Git Remotes"
|
||||||
|
git -C "${REPO_ROOT}" remote -v || true
|
||||||
|
|
||||||
|
printf '\nCurrent branch:\n'
|
||||||
|
git -C "${REPO_ROOT}" branch -vv || true
|
||||||
|
|
||||||
|
printf '\nGitea deploy key expected path: %s\n' "${LAB_GITEA_REPO_SSH_KEY_PATH:-${HOME}/.ssh/id_ed25519.pub}"
|
||||||
|
printf 'Gitea deploy key title: %s\n' "${LAB_GITEA_REPO_DEPLOY_KEY_TITLE:-debian-host-my-homelab-configs}"
|
||||||
|
printf 'Gitea deploy key read-only: %s\n' "${LAB_GITEA_REPO_DEPLOY_KEY_READ_ONLY:-false}"
|
||||||
|
if [[ "${LAB_GITEA_REPO_DEPLOY_KEY_READ_ONLY:-false}" != "true" ]]; then
|
||||||
|
printf ' warn: deploy key is expected to be write-capable for Actions/deploy mirroring\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tailnet_access() {
|
||||||
|
section "Tailscale"
|
||||||
|
if command -v tailscale >/dev/null 2>&1; then
|
||||||
|
tailscale ip -4 || true
|
||||||
|
tailscale status || true
|
||||||
|
else
|
||||||
|
printf 'tailscale command not installed\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\nRepo-managed ACL validation:\n'
|
||||||
|
"${REPO_ROOT}/scripts/validate-tailnet-policy" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
kubernetes_access() {
|
||||||
|
section "Kubernetes Access"
|
||||||
|
if ! command -v kubectl >/dev/null 2>&1; then
|
||||||
|
printf 'kubectl not installed\n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ ! -s "${KUBECONFIG}" ]]; then
|
||||||
|
printf 'kubeconfig missing: %s\n' "${KUBECONFIG}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'KUBECONFIG: %s\n' "${KUBECONFIG}"
|
||||||
|
kubectl --kubeconfig "${KUBECONFIG}" config current-context 2>/dev/null || true
|
||||||
|
|
||||||
|
printf '\nCan current user perform broad actions?\n'
|
||||||
|
for verb in get list create update delete; do
|
||||||
|
printf '%-10s ' "${verb}"
|
||||||
|
kubectl --kubeconfig "${KUBECONFIG}" auth can-i "${verb}" pods --all-namespaces 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\nClusterRoleBindings to cluster-admin:\n'
|
||||||
|
kubectl --kubeconfig "${KUBECONFIG}" get clusterrolebinding -o jsonpath='{range .items[?(@.roleRef.name=="cluster-admin")]}{.metadata.name}{" -> "}{range .subjects[*]}{.kind}{"/"}{.namespace}{"/"}{.name}{" "}{end}{"\n"}{end}' 2>/dev/null || true
|
||||||
|
|
||||||
|
printf '\nAutomation service accounts:\n'
|
||||||
|
kubectl --kubeconfig "${KUBECONFIG}" get serviceaccounts -A 2>/dev/null |
|
||||||
|
awk 'NR == 1 || /argocd|gitea|runner|kyverno|tetragon|prometheus|jeannie|homelab/'
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
Read-only kubeconfig target:
|
||||||
|
- Create a dedicated read-only ClusterRole/Binding for dashboards and audits.
|
||||||
|
- Keep admin kubeconfig only on the Debian control host.
|
||||||
|
- Use separate service accounts for automation instead of reusing admin.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
runner_access() {
|
||||||
|
section "Automation Runners"
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl is-active homelab-gitea-runner.service 2>/dev/null || true
|
||||||
|
systemctl status homelab-gitea-runner.service --no-pager -l 2>/dev/null | sed -n '1,12p' || true
|
||||||
|
else
|
||||||
|
printf 'systemctl unavailable\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
ssh_inventory
|
||||||
|
git_access
|
||||||
|
tailnet_access
|
||||||
|
kubernetes_access
|
||||||
|
runner_access
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Loading…
Reference in New Issue