1067 lines
42 KiB
Bash
Executable File
1067 lines
42 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)"
|
|
RENDERER="$ROOT/scripts/validate/render-observability-access.sh"
|
|
|
|
# The renderer is intentionally sourced: these tests exercise the same parser and
|
|
# manifest assertions used by executable rendering, without downloading charts or
|
|
# contacting a Kubernetes API server.
|
|
source "$RENDERER"
|
|
|
|
ASSERTIONS=0
|
|
WORK=''
|
|
PUBLISH_ROOTS=()
|
|
|
|
cleanup() {
|
|
local rc=$?
|
|
trap - EXIT HUP INT TERM
|
|
if [[ -n "$WORK" && "$WORK" == /tmp/platform-observability-access-test.* &&
|
|
-d "$WORK" && ! -L "$WORK" ]]; then
|
|
rm -rf -- "$WORK"
|
|
fi
|
|
local publish_root
|
|
for publish_root in "${PUBLISH_ROOTS[@]}"; do
|
|
if [[ "$publish_root" == /tmp/platform-observability-metrics.?????? &&
|
|
-d "$publish_root" && ! -L "$publish_root" ]]; then
|
|
rm -rf -- "$publish_root"
|
|
fi
|
|
done
|
|
exit "$rc"
|
|
}
|
|
trap cleanup EXIT HUP INT TERM
|
|
|
|
pass() {
|
|
ASSERTIONS=$((ASSERTIONS + 1))
|
|
printf 'PASS: %s\n' "$1"
|
|
}
|
|
|
|
expect_pass() {
|
|
local label="$1"
|
|
shift
|
|
if "$@" >/dev/null 2>&1; then
|
|
pass "$label"
|
|
else
|
|
printf 'FAIL: expected success: %s\n' "$label" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
expect_fail() {
|
|
local label="$1"
|
|
shift
|
|
if "$@" >/dev/null 2>&1; then
|
|
printf 'FAIL: expected rejection: %s\n' "$label" >&2
|
|
exit 1
|
|
fi
|
|
pass "$label"
|
|
}
|
|
|
|
expect_output() {
|
|
local label="$1" expected="$2"
|
|
shift 2
|
|
local actual
|
|
actual="$($@)" || {
|
|
printf 'FAIL: output command failed: %s\n' "$label" >&2
|
|
exit 1
|
|
}
|
|
if [[ "$actual" != "$expected" ]]; then
|
|
printf 'FAIL: unexpected output: %s\nexpected:\n%s\nactual:\n%s\n' \
|
|
"$label" "$expected" "$actual" >&2
|
|
exit 1
|
|
fi
|
|
pass "$label"
|
|
}
|
|
|
|
mutate_yaml() {
|
|
local source="$1" destination="$2" mutation="$3"
|
|
python3 - "$source" "$destination" "$mutation" <<'PY'
|
|
import sys
|
|
import yaml
|
|
|
|
class FixtureLoader(yaml.SafeLoader):
|
|
pass
|
|
|
|
|
|
FixtureLoader.add_constructor(
|
|
"tag:yaml.org,2002:value",
|
|
lambda loader, node: loader.construct_scalar(node),
|
|
)
|
|
|
|
source, destination, mutation = sys.argv[1:]
|
|
with open(source, "r", encoding="utf-8") as stream:
|
|
items = [item for item in yaml.load_all(stream, Loader=FixtureLoader) if item is not None]
|
|
|
|
|
|
def object_named(kind, name):
|
|
matches = [
|
|
item for item in items
|
|
if item.get("kind") == kind and (item.get("metadata") or {}).get("name") == name
|
|
]
|
|
if len(matches) != 1:
|
|
raise SystemExit(f"fixture object is not exact: {kind}/{name}")
|
|
return matches[0]
|
|
|
|
|
|
if mutation == "grafana-public-service":
|
|
object_named("Service", "grafana")["spec"]["type"] = "LoadBalancer"
|
|
elif mutation == "grafana-missing-class":
|
|
object_named("Ingress", "grafana")["spec"].pop("ingressClassName", None)
|
|
elif mutation == "grafana-main-token":
|
|
deployment = object_named("Deployment", "grafana")
|
|
main = next(c for c in deployment["spec"]["template"]["spec"]["containers"] if c["name"] == "grafana")
|
|
main.setdefault("volumeMounts", []).append({
|
|
"name": "dashboard-sidecar-api-access",
|
|
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
|
|
"readOnly": True,
|
|
})
|
|
elif mutation == "grafana-cluster-role":
|
|
role = object_named("Role", "grafana-dashboard-sidecar")
|
|
role["kind"] = "ClusterRole"
|
|
role["metadata"].pop("namespace", None)
|
|
elif mutation == "oidc-viewer-fallback":
|
|
config = object_named("ConfigMap", "grafana")
|
|
config["data"]["grafana.ini"] = config["data"]["grafana.ini"].replace("|| null", "|| 'Viewer'")
|
|
elif mutation == "oidc-editor":
|
|
config = object_named("ConfigMap", "grafana")
|
|
config["data"]["grafana.ini"] = config["data"]["grafana.ini"].replace("&& 'Viewer'", "&& 'Editor'")
|
|
elif mutation == "oidc-grafana-admin":
|
|
config = object_named("ConfigMap", "grafana")
|
|
config["data"]["grafana.ini"] = config["data"]["grafana.ini"].replace(
|
|
"allow_assign_grafana_admin = false", "allow_assign_grafana_admin = true"
|
|
)
|
|
elif mutation == "oidc-group-missing":
|
|
config = object_named("ConfigMap", "grafana")
|
|
config["data"]["grafana.ini"] = config["data"]["grafana.ini"].replace(
|
|
"/platform-observability-viewers", "/not-authorized"
|
|
)
|
|
elif mutation == "blackbox-arbitrary-target":
|
|
probe = object_named("Probe", "platform-public-edge")
|
|
probe["spec"]["targets"]["staticConfig"]["static"].append("https://example.invalid/")
|
|
elif mutation == "blackbox-public-service":
|
|
object_named("Service", "blackbox-exporter")["spec"]["type"] = "NodePort"
|
|
elif mutation == "missing-instance-label":
|
|
object_named("Probe", "platform-public-edge")["metadata"]["labels"].pop(
|
|
"observability.hyeonworks.com/instance", None
|
|
)
|
|
elif mutation == "outside-target":
|
|
items.append({
|
|
"apiVersion": "monitoring.coreos.com/v1",
|
|
"kind": "ServiceMonitor",
|
|
"metadata": {
|
|
"name": "rabbitmq",
|
|
"namespace": "messaging",
|
|
"labels": {"observability.hyeonworks.com/instance": "home"},
|
|
},
|
|
"spec": {"selector": {"matchLabels": {"app": "rabbitmq"}}, "endpoints": [{"port": "metrics"}]},
|
|
})
|
|
elif mutation == "forbidden-product":
|
|
items.append({
|
|
"apiVersion": "monitoring.coreos.com/v1",
|
|
"kind": "PrometheusRule",
|
|
"metadata": {
|
|
"name": "platform-jvm",
|
|
"namespace": "observability",
|
|
"labels": {"observability.hyeonworks.com/instance": "home"},
|
|
},
|
|
"spec": {"groups": [{"name": "jvm", "rules": [{"alert": "JVMHeapHigh", "expr": "vector(0)"}]}]},
|
|
})
|
|
elif mutation == "alertmanager-inline-secret":
|
|
items.append({
|
|
"apiVersion": "v1",
|
|
"kind": "Secret",
|
|
"metadata": {"name": "inline-fixture", "namespace": "observability"},
|
|
"type": "Opaque",
|
|
"stringData": {"password": "forbidden-fixture-value"},
|
|
})
|
|
elif mutation == "alertmanager-missing-title":
|
|
config = object_named("AlertmanagerConfig", "platform-alertmanager")
|
|
receiver = next(
|
|
receiver for receiver in config["spec"]["receivers"]
|
|
if receiver["name"] == "platform-slack"
|
|
)
|
|
receiver["slackConfigs"][0].pop("title")
|
|
elif mutation == "dns-public-address":
|
|
config = object_named("ConfigMap", "coredns-custom")
|
|
config["data"]["learn-hyeonworks.server"] = config["data"]["learn-hyeonworks.server"].replace(
|
|
"192.168.0.107 grafana.learn.hyeonworks.com",
|
|
"203.0.113.10 grafana.learn.hyeonworks.com",
|
|
)
|
|
elif mutation == "grafana-broad-policy":
|
|
policy = object_named("NetworkPolicy", "observability-allow-grafana-ingress")
|
|
policy["spec"] = {"podSelector": {}, "policyTypes": ["Ingress"], "ingress": [{}]}
|
|
elif mutation == "blackbox-broad-egress":
|
|
policy = object_named("NetworkPolicy", "observability-allow-blackbox-egress")
|
|
policy["spec"] = {"podSelector": {}, "policyTypes": ["Egress"], "egress": [{}]}
|
|
elif mutation == "targets-broad-egress":
|
|
policy = object_named("NetworkPolicy", "observability-allow-prometheus-platform-targets")
|
|
policy["spec"] = {"podSelector": {}, "policyTypes": ["Egress"], "egress": [{}]}
|
|
elif mutation == "grafana-extra-allow-all":
|
|
items.append({
|
|
"apiVersion": "networking.k8s.io/v1", "kind": "NetworkPolicy",
|
|
"metadata": {"name": "observability-extra-grafana-allow-all", "namespace": "observability"},
|
|
"spec": {
|
|
"podSelector": {"matchLabels": {
|
|
"app.kubernetes.io/name": "grafana", "app.kubernetes.io/instance": "grafana",
|
|
}},
|
|
"policyTypes": ["Ingress", "Egress"], "ingress": [{}], "egress": [{}],
|
|
},
|
|
})
|
|
elif mutation == "blackbox-extra-allow-all":
|
|
items.append({
|
|
"apiVersion": "networking.k8s.io/v1", "kind": "NetworkPolicy",
|
|
"metadata": {"name": "observability-extra-blackbox-allow-all", "namespace": "observability"},
|
|
"spec": {
|
|
"podSelector": {"matchLabels": {
|
|
"app.kubernetes.io/name": "prometheus-blackbox-exporter",
|
|
"app.kubernetes.io/instance": "blackbox-exporter",
|
|
}},
|
|
"policyTypes": ["Ingress", "Egress"], "ingress": [{}], "egress": [{}],
|
|
},
|
|
})
|
|
elif mutation == "targets-extra-allow-all":
|
|
items.append({
|
|
"apiVersion": "networking.k8s.io/v1", "kind": "NetworkPolicy",
|
|
"metadata": {"name": "observability-extra-prometheus-allow-all", "namespace": "observability"},
|
|
"spec": {
|
|
"podSelector": {"matchLabels": {
|
|
"app.kubernetes.io/name": "prometheus",
|
|
"app.kubernetes.io/instance": "observability-core-kube-pr-prometheus",
|
|
}},
|
|
"policyTypes": ["Egress"], "egress": [{}],
|
|
},
|
|
})
|
|
elif mutation == "alertmanager-extra-allow-all":
|
|
items.append({
|
|
"apiVersion": "networking.k8s.io/v1", "kind": "NetworkPolicy",
|
|
"metadata": {"name": "observability-extra-alertmanager-allow-all", "namespace": "observability"},
|
|
"spec": {
|
|
"podSelector": {"matchLabels": {
|
|
"app.kubernetes.io/name": "alertmanager",
|
|
"app.kubernetes.io/instance": "observability-core-kube-pr-alertmanager",
|
|
}},
|
|
"policyTypes": ["Egress"], "egress": [{}],
|
|
},
|
|
})
|
|
else:
|
|
raise SystemExit(f"unknown mutation: {mutation}")
|
|
|
|
with open(destination, "w", encoding="utf-8") as stream:
|
|
yaml.safe_dump_all(items, stream, explicit_start=True, sort_keys=False)
|
|
PY
|
|
}
|
|
|
|
write_inventory_phase() {
|
|
local root="$1" phase="$2" pool="$3" provenance="$4" metric="$5"
|
|
local directory="$root/$phase" hash
|
|
mkdir -m 0700 -- "$directory"
|
|
printf '%s\n' \
|
|
'{' \
|
|
' "schema": "platform-observability-metric-inventory/v1",' \
|
|
" \"phase\": \"$phase\"," \
|
|
' "captured_at_utc": "2026-08-12T00:00:00Z",' \
|
|
' "context": "default",' \
|
|
' "api_server": "https://127.0.0.1:6443",' \
|
|
' "prometheus": "observability/observability-core-kube-pr-prometheus",' \
|
|
' "preexisting_verify_only": ["grafana"],' \
|
|
' "targets": [' \
|
|
' {' \
|
|
" \"scrape_pool\": \"$pool\"," \
|
|
' "scrape_url": "http://127.0.0.1:9090/metrics",' \
|
|
' "health": "up",' \
|
|
' "last_error": "",' \
|
|
" \"provenance\": \"$provenance\"," \
|
|
" \"metrics\": [{\"name\": \"$metric\", \"label_names\": [\"__name__\", \"instance\", \"namespace\"]}]" \
|
|
' }' \
|
|
' ]' \
|
|
'}' >"$directory/inventory.json"
|
|
chmod 0600 "$directory/inventory.json"
|
|
hash="$(sha256sum "$directory/inventory.json" | awk '{print $1}')"
|
|
printf '%s inventory.json\n' "$hash" >"$directory/inventory.sha256"
|
|
chmod 0600 "$directory/inventory.sha256"
|
|
}
|
|
|
|
make_publish_root() {
|
|
local root
|
|
root="$(mktemp -d /tmp/platform-observability-metrics.XXXXXX)"
|
|
chmod 0700 "$root"
|
|
PUBLISH_ROOTS+=("$root")
|
|
write_inventory_phase "$root" target-initial \
|
|
serviceMonitor/observability/grafana/0 preexisting-verify-only up
|
|
write_inventory_phase "$root" post-substrate \
|
|
probe/observability/platform-public-edge substrate-owned probe_success
|
|
PUBLISH_ROOT=$root
|
|
}
|
|
|
|
publication_tree_fingerprint() {
|
|
local root=$1
|
|
python3 - "$root" <<'PY'
|
|
import hashlib
|
|
import os
|
|
import stat
|
|
import sys
|
|
|
|
|
|
root = sys.argv[1]
|
|
records = []
|
|
|
|
|
|
def record(path, relative):
|
|
metadata = os.lstat(path)
|
|
content_hash = "-"
|
|
if stat.S_ISREG(metadata.st_mode):
|
|
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
|
try:
|
|
digest = hashlib.sha256()
|
|
while True:
|
|
chunk = os.read(descriptor, 1024 * 1024)
|
|
if not chunk:
|
|
break
|
|
digest.update(chunk)
|
|
content_hash = digest.hexdigest()
|
|
finally:
|
|
os.close(descriptor)
|
|
records.append("|".join((
|
|
relative,
|
|
str(metadata.st_dev),
|
|
str(metadata.st_ino),
|
|
oct(stat.S_IFMT(metadata.st_mode)),
|
|
oct(stat.S_IMODE(metadata.st_mode)),
|
|
str(metadata.st_uid),
|
|
str(metadata.st_gid),
|
|
str(metadata.st_nlink),
|
|
str(metadata.st_size),
|
|
str(metadata.st_mtime_ns),
|
|
str(metadata.st_ctime_ns),
|
|
content_hash,
|
|
)))
|
|
if stat.S_ISDIR(metadata.st_mode):
|
|
with os.scandir(path) as entries:
|
|
for entry in sorted(entries, key=lambda item: item.name):
|
|
child_relative = entry.name if relative == "." else f"{relative}/{entry.name}"
|
|
record(entry.path, child_relative)
|
|
|
|
|
|
record(root, ".")
|
|
print(hashlib.sha256("\0".join(records).encode()).hexdigest())
|
|
PY
|
|
}
|
|
|
|
make_inventory_only_clone() {
|
|
local source=$1 root phase name
|
|
root="$(mktemp -d /tmp/platform-observability-metrics.XXXXXX)"
|
|
chmod 0700 "$root"
|
|
PUBLISH_ROOTS+=("$root")
|
|
for phase in target-initial post-substrate; do
|
|
mkdir -m 0700 -- "$root/$phase"
|
|
for name in inventory.json inventory.sha256; do
|
|
cp --no-dereference --reflink=never -- \
|
|
"$source/$phase/$name" "$root/$phase/$name"
|
|
chmod 0600 "$root/$phase/$name"
|
|
done
|
|
done
|
|
PUBLISH_ROOT=$root
|
|
}
|
|
|
|
inventory_clone_matches_source() {
|
|
local source=$1 clone=$2 phase name uid
|
|
uid=$(id -u)
|
|
for phase in target-initial post-substrate; do
|
|
[[ -d "$source/$phase" && ! -L "$source/$phase" ]] || return 1
|
|
[[ -d "$clone/$phase" && ! -L "$clone/$phase" ]] || return 1
|
|
[[ "$(stat -c '%u|%a' -- "$source/$phase")" == "$uid|700" ]] || return 1
|
|
[[ "$(stat -c '%u|%a' -- "$clone/$phase")" == "$uid|700" ]] || return 1
|
|
for name in inventory.json inventory.sha256; do
|
|
[[ -f "$source/$phase/$name" && ! -L "$source/$phase/$name" ]] || return 1
|
|
[[ -f "$clone/$phase/$name" && ! -L "$clone/$phase/$name" ]] || return 1
|
|
[[ "$(stat -c '%u|%a|%h' -- "$source/$phase/$name")" == "$uid|600|1" ]] || return 1
|
|
[[ "$(stat -c '%u|%a|%h' -- "$clone/$phase/$name")" == "$uid|600|1" ]] || return 1
|
|
cmp -s "$source/$phase/$name" "$clone/$phase/$name" || return 1
|
|
done
|
|
done
|
|
}
|
|
|
|
published_complete_exact() {
|
|
local root=$1 source=$2 name uid
|
|
local expected_root expected_source
|
|
expected_root=$'alertmanager.yaml\nblackbox.yaml\ndashboards.yaml\ngrafana.yaml\npost-substrate\nprivate-dns.yaml\nrules.yaml\ntarget-initial\ntargets.yaml'
|
|
expected_source=$'alertmanager.yaml\nblackbox.yaml\ndashboards.yaml\ngrafana.yaml\nprivate-dns.yaml\nrules.yaml\ntargets.yaml'
|
|
[[ "$(find "$root" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort)" == "$expected_root" ]] || return 1
|
|
[[ "$(find "$source" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort)" == "$expected_source" ]] || return 1
|
|
uid=$(id -u)
|
|
for name in \
|
|
grafana.yaml blackbox.yaml targets.yaml dashboards.yaml rules.yaml alertmanager.yaml private-dns.yaml; do
|
|
[[ -f "$source/$name" && ! -L "$source/$name" ]] || return 1
|
|
[[ -f "$root/$name" && ! -L "$root/$name" ]] || return 1
|
|
[[ "$(stat -c '%u|%a|%h' -- "$source/$name")" == "$uid|600|1" ]] || return 1
|
|
[[ "$(stat -c '%u|%a|%h' -- "$root/$name")" == "$uid|600|1" ]] || return 1
|
|
cmp -s "$source/$name" "$root/$name" || return 1
|
|
done
|
|
}
|
|
|
|
published_rules_alerts_exact() {
|
|
local root=$1 source=$2 name
|
|
local expected=$'alertmanager.yaml\ndashboards.yaml\npost-substrate\nrules.yaml\ntarget-initial'
|
|
[[ "$(find "$root" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort)" == "$expected" ]] || return 1
|
|
for name in dashboards.yaml rules.yaml alertmanager.yaml; do
|
|
[[ -f "$root/$name" && ! -L "$root/$name" && "$(stat -c %a -- "$root/$name")" == 600 ]] || return 1
|
|
cmp -s "$source/$name" "$root/$name" || return 1
|
|
done
|
|
}
|
|
|
|
collision_left_no_partial_publish() {
|
|
local root=$1
|
|
[[ -f "$root/rules.yaml" && ! -e "$root/dashboards.yaml" && ! -e "$root/alertmanager.yaml" ]] || return 1
|
|
}
|
|
|
|
complete_collision_left_no_partial() {
|
|
local root=$1 uid expected
|
|
expected=$'post-substrate\nprivate-dns.yaml\ntarget-initial'
|
|
[[ "$(find "$root" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort)" == "$expected" ]] || return 1
|
|
[[ -f "$root/private-dns.yaml" && ! -L "$root/private-dns.yaml" ]] || return 1
|
|
uid=$(id -u)
|
|
[[ "$(stat -c '%u|%a|%h' -- "$root/private-dns.yaml")" == "$uid|600|1" ]] || return 1
|
|
[[ "$(<"$root/private-dns.yaml")" == 'preexisting complete collision' ]]
|
|
}
|
|
|
|
secure_render_context_creates_private_files() {
|
|
local directory=$1
|
|
(
|
|
access_secure_render_context
|
|
printf '%s\n' rendered >"$directory/rendered.yaml"
|
|
) || return 1
|
|
[[ "$(stat -c %a -- "$directory/rendered.yaml")" == 600 ]]
|
|
}
|
|
|
|
WORK="$(mktemp -d /tmp/platform-observability-access-test.XXXXXX)"
|
|
chmod 0700 "$WORK"
|
|
|
|
grafana="$WORK/grafana.yaml"
|
|
cat >"$grafana" <<'YAML'
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: Role
|
|
metadata:
|
|
name: grafana-dashboard-sidecar
|
|
namespace: observability
|
|
rules:
|
|
- apiGroups: [""]
|
|
resources: [configmaps]
|
|
verbs: [list, watch]
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: RoleBinding
|
|
metadata:
|
|
name: grafana-dashboard-sidecar
|
|
namespace: observability
|
|
roleRef:
|
|
apiGroup: rbac.authorization.k8s.io
|
|
kind: Role
|
|
name: grafana-dashboard-sidecar
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: grafana
|
|
namespace: observability
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: grafana
|
|
namespace: observability
|
|
data:
|
|
grafana.ini: |
|
|
[auth]
|
|
login_maximum_lifetime_duration = 8h
|
|
login_maximum_inactive_lifetime_duration = 30m
|
|
[auth.anonymous]
|
|
enabled = false
|
|
[auth.generic_oauth]
|
|
enabled = true
|
|
scopes = openid profile email
|
|
groups_attribute_path = groups
|
|
allowed_groups = /platform-observability-admins /platform-observability-viewers
|
|
role_attribute_strict = true
|
|
allow_assign_grafana_admin = false
|
|
role_attribute_path = contains(groups[*], '/platform-observability-admins') && 'Admin' || contains(groups[*], '/platform-observability-viewers') && 'Viewer' || null
|
|
auth_url = https://id.learn.hyeonworks.com/realms/hyeonworks/protocol/openid-connect/auth
|
|
token_url = https://id.learn.hyeonworks.com/realms/hyeonworks/protocol/openid-connect/token
|
|
api_url = https://id.learn.hyeonworks.com/realms/hyeonworks/protocol/openid-connect/userinfo
|
|
signout_redirect_url = https://id.learn.hyeonworks.com/realms/hyeonworks/protocol/openid-connect/logout
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: grafana
|
|
namespace: observability
|
|
spec:
|
|
type: ClusterIP
|
|
selector:
|
|
app.kubernetes.io/name: grafana
|
|
app.kubernetes.io/instance: grafana
|
|
ports:
|
|
- name: service
|
|
port: 80
|
|
targetPort: grafana
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: grafana
|
|
namespace: observability
|
|
spec:
|
|
ingressClassName: traefik
|
|
rules:
|
|
- host: grafana.learn.hyeonworks.com
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: grafana
|
|
port:
|
|
number: 80
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: grafana
|
|
namespace: observability
|
|
spec:
|
|
strategy:
|
|
type: Recreate
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: grafana
|
|
app.kubernetes.io/instance: grafana
|
|
spec:
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: grafana
|
|
containers:
|
|
- name: grafana-sc-dashboard
|
|
image: quay.io/kiwigrid/k8s-sidecar:2.10.0@sha256:129877c81acf2bc8c3fa000e89a62e020eb89d41ceb94767c657aef5bb0cc0d3
|
|
volumeMounts:
|
|
- name: dashboard-sidecar-api-access
|
|
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
|
|
readOnly: true
|
|
- name: grafana
|
|
image: docker.io/grafana/grafana:13.1.1@sha256:f33c692ba1a5ee15724cf6b22db65e9de39dde14d80f7d73a9546e3fc917270b
|
|
env:
|
|
- name: GF_SECURITY_ADMIN_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: grafana-admin
|
|
key: admin-password
|
|
- name: GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: grafana-keycloak-oidc
|
|
key: client-secret
|
|
volumes:
|
|
- name: dashboard-sidecar-api-access
|
|
projected:
|
|
defaultMode: 420
|
|
sources:
|
|
- serviceAccountToken:
|
|
expirationSeconds: 3600
|
|
path: token
|
|
- configMap:
|
|
name: kube-root-ca.crt
|
|
items:
|
|
- key: ca.crt
|
|
path: ca.crt
|
|
- downwardAPI:
|
|
items:
|
|
- path: namespace
|
|
fieldRef:
|
|
apiVersion: v1
|
|
fieldPath: metadata.namespace
|
|
---
|
|
apiVersion: monitoring.coreos.com/v1
|
|
kind: ServiceMonitor
|
|
metadata:
|
|
name: grafana
|
|
namespace: observability
|
|
labels:
|
|
observability.hyeonworks.com/instance: home
|
|
spec:
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: grafana
|
|
app.kubernetes.io/instance: grafana
|
|
endpoints:
|
|
- port: service
|
|
path: /metrics
|
|
YAML
|
|
|
|
kubectl kustomize "$ROOT/services/observability/network-policies" >"$WORK/core-policies.yaml"
|
|
|
|
blackbox="$WORK/blackbox.yaml"
|
|
cat >"$blackbox" <<'YAML'
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: blackbox-exporter
|
|
namespace: observability
|
|
spec:
|
|
type: ClusterIP
|
|
selector:
|
|
app.kubernetes.io/name: prometheus-blackbox-exporter
|
|
app.kubernetes.io/instance: blackbox-exporter
|
|
ports:
|
|
- name: http
|
|
port: 9115
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: blackbox-exporter
|
|
namespace: observability
|
|
spec:
|
|
strategy:
|
|
type: Recreate
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: prometheus-blackbox-exporter
|
|
app.kubernetes.io/instance: blackbox-exporter
|
|
spec:
|
|
automountServiceAccountToken: false
|
|
containers:
|
|
- name: blackbox-exporter
|
|
image: quay.io/prometheus/blackbox-exporter:v0.28.0@sha256:43027b43fb785b7c5adc53bd3b5dbc1a258270a2e8aff24f477b45c4e38dac68
|
|
---
|
|
apiVersion: monitoring.coreos.com/v1
|
|
kind: ServiceMonitor
|
|
metadata:
|
|
name: blackbox-exporter
|
|
namespace: observability
|
|
labels:
|
|
observability.hyeonworks.com/instance: home
|
|
spec:
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: prometheus-blackbox-exporter
|
|
app.kubernetes.io/instance: blackbox-exporter
|
|
endpoints:
|
|
- port: http
|
|
path: /metrics
|
|
---
|
|
apiVersion: monitoring.coreos.com/v1
|
|
kind: Probe
|
|
metadata:
|
|
name: platform-public-edge
|
|
namespace: observability
|
|
labels:
|
|
observability.hyeonworks.com/instance: home
|
|
spec:
|
|
jobName: blackbox-public-edge
|
|
module: http_2xx
|
|
prober:
|
|
url: blackbox-exporter.observability.svc.cluster.local:9115
|
|
scheme: http
|
|
path: /probe
|
|
proxyFromEnvironment: false
|
|
targets:
|
|
staticConfig:
|
|
labels:
|
|
observability.hyeonworks.com/probe-group: public-edge
|
|
static:
|
|
- https://git.learn.hyeonworks.com/api/healthz
|
|
- https://id.learn.hyeonworks.com/realms/hyeonworks/.well-known/openid-configuration
|
|
---
|
|
apiVersion: monitoring.coreos.com/v1
|
|
kind: Probe
|
|
metadata:
|
|
name: platform-private-edge
|
|
namespace: observability
|
|
labels:
|
|
observability.hyeonworks.com/instance: home
|
|
spec:
|
|
jobName: blackbox-private-edge
|
|
module: http_private_edge_403
|
|
prober:
|
|
url: blackbox-exporter.observability.svc.cluster.local:9115
|
|
scheme: http
|
|
path: /probe
|
|
proxyFromEnvironment: false
|
|
targets:
|
|
staticConfig:
|
|
labels:
|
|
observability.hyeonworks.com/probe-group: private-edge
|
|
static:
|
|
- https://grafana.learn.hyeonworks.com/
|
|
- https://storage-admin.learn.hyeonworks.com/
|
|
- https://db-admin.learn.hyeonworks.com/
|
|
---
|
|
apiVersion: monitoring.coreos.com/v1
|
|
kind: Probe
|
|
metadata:
|
|
name: platform-private-internal
|
|
namespace: observability
|
|
labels:
|
|
observability.hyeonworks.com/instance: home
|
|
spec:
|
|
jobName: blackbox-private-internal
|
|
module: http_private_internal_200
|
|
prober:
|
|
url: blackbox-exporter.observability.svc.cluster.local:9115
|
|
scheme: http
|
|
path: /probe
|
|
proxyFromEnvironment: false
|
|
targets:
|
|
staticConfig:
|
|
labels:
|
|
observability.hyeonworks.com/probe-group: private-internal
|
|
static:
|
|
- http://grafana.observability.svc.cluster.local/api/health
|
|
- http://pgadmin.platform-admin.svc.cluster.local/misc/ping
|
|
- http://minio-aistor-console.object-storage.svc.cluster.local:9090/
|
|
relabelingConfigs:
|
|
- sourceLabels: [__param_target]
|
|
regex: http://grafana[.]observability[.]svc[.]cluster[.]local/api/health
|
|
targetLabel: __param_hostname
|
|
replacement: grafana.learn.hyeonworks.com
|
|
- sourceLabels: [__param_target]
|
|
regex: http://pgadmin[.]platform-admin[.]svc[.]cluster[.]local/misc/ping
|
|
targetLabel: __param_hostname
|
|
replacement: db-admin.learn.hyeonworks.com
|
|
- sourceLabels: [__param_target]
|
|
regex: http://minio-aistor-console[.]object-storage[.]svc[.]cluster[.]local:9090/
|
|
targetLabel: __param_hostname
|
|
replacement: storage-admin.learn.hyeonworks.com
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: observability-allow-prometheus-to-blackbox
|
|
namespace: observability
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: prometheus-blackbox-exporter
|
|
app.kubernetes.io/instance: blackbox-exporter
|
|
policyTypes: [Ingress]
|
|
ingress:
|
|
- from:
|
|
- podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: prometheus
|
|
app.kubernetes.io/instance: observability-core-kube-pr-prometheus
|
|
ports:
|
|
- protocol: TCP
|
|
port: 9115
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: observability-allow-blackbox-egress
|
|
namespace: observability
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: prometheus-blackbox-exporter
|
|
app.kubernetes.io/instance: blackbox-exporter
|
|
policyTypes: [Egress]
|
|
egress:
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: kube-system
|
|
podSelector:
|
|
matchLabels:
|
|
k8s-app: kube-dns
|
|
ports:
|
|
- {protocol: UDP, port: 53}
|
|
- {protocol: TCP, port: 53}
|
|
- to:
|
|
- ipBlock: {cidr: 192.168.0.107/32}
|
|
ports:
|
|
- {protocol: TCP, port: 443}
|
|
- to:
|
|
- podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: grafana
|
|
app.kubernetes.io/instance: grafana
|
|
ports:
|
|
- {protocol: TCP, port: 3000}
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: platform-admin
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: pgadmin4
|
|
app.kubernetes.io/instance: pgadmin
|
|
ports:
|
|
- {protocol: TCP, port: 5050}
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: object-storage
|
|
podSelector:
|
|
matchLabels:
|
|
aistor.min.io/objectStore: minio-aistor
|
|
ports:
|
|
- {protocol: TCP, port: 9090}
|
|
YAML
|
|
|
|
kubectl kustomize "$ROOT/services/observability/targets" >"$WORK/targets.yaml"
|
|
kubectl kustomize "$ROOT/services/observability/dashboards/platform" >"$WORK/dashboards.yaml"
|
|
kubectl kustomize "$ROOT/services/observability/rules/platform" >"$WORK/rules.yaml"
|
|
|
|
cat >"$WORK/alertmanager-base.yaml" <<'YAML'
|
|
apiVersion: monitoring.coreos.com/v1
|
|
kind: Alertmanager
|
|
metadata:
|
|
name: observability-core-kube-pr-alertmanager
|
|
namespace: observability
|
|
spec:
|
|
alertmanagerConfiguration:
|
|
name: platform-alertmanager
|
|
YAML
|
|
kubectl kustomize "$ROOT/services/observability/alerting" >"$WORK/alerting.yaml"
|
|
python3 - "$WORK/alertmanager-base.yaml" "$WORK/alerting.yaml" "$WORK/alertmanager.yaml" <<'PY'
|
|
import sys
|
|
import yaml
|
|
|
|
class FixtureLoader(yaml.SafeLoader):
|
|
pass
|
|
|
|
FixtureLoader.add_constructor(
|
|
"tag:yaml.org,2002:value",
|
|
lambda loader, node: loader.construct_scalar(node),
|
|
)
|
|
|
|
items = []
|
|
for path in sys.argv[1:3]:
|
|
with open(path, "r", encoding="utf-8") as stream:
|
|
items.extend(item for item in yaml.load_all(stream, Loader=FixtureLoader) if item is not None)
|
|
with open(sys.argv[3], "w", encoding="utf-8") as stream:
|
|
yaml.safe_dump_all(items, stream, explicit_start=True, sort_keys=False)
|
|
PY
|
|
|
|
kubectl kustomize "$ROOT/infrastructure/networking/private-dns/kubernetes" >"$WORK/private-dns.yaml"
|
|
kubectl kustomize "$ROOT/infrastructure/networking/traefik/overlays/trust" >"$WORK/traefik-trust.yaml"
|
|
kubectl kustomize "$ROOT/infrastructure/networking/traefik/overlays/observe" >"$WORK/traefik-observe.yaml"
|
|
|
|
expected_complete=$'grafana.yaml\nblackbox.yaml\ntargets.yaml\ndashboards.yaml\nrules.yaml\nalertmanager.yaml\nprivate-dns.yaml'
|
|
expect_output 'complete mode has the exact seven access artifacts' "$expected_complete" access_output_names complete
|
|
expect_output 'grafana mode has one artifact' 'grafana.yaml' access_output_names grafana
|
|
expect_output 'blackbox mode has one artifact' 'blackbox.yaml' access_output_names blackbox
|
|
expect_output 'targets mode has one artifact' 'targets.yaml' access_output_names targets
|
|
expect_output 'rules-alerts mode has three artifacts' $'dashboards.yaml\nrules.yaml\nalertmanager.yaml' access_output_names rules-alerts
|
|
expect_fail 'unknown component is rejected' access_output_names arbitrary
|
|
|
|
expect_pass 'Grafana private access contract' assert_access_grafana_contract "$grafana" "$WORK/core-policies.yaml"
|
|
expect_pass 'Grafana and sidecar images are digest pinned' assert_pinned_images "$grafana"
|
|
|
|
mutate_yaml "$WORK/core-policies.yaml" "$WORK/grafana-broad-policy.yaml" grafana-broad-policy
|
|
expect_fail 'broad Grafana ingress NetworkPolicy is rejected' \
|
|
assert_access_grafana_contract "$grafana" "$WORK/grafana-broad-policy.yaml"
|
|
mutate_yaml "$WORK/core-policies.yaml" "$WORK/grafana-extra-policy.yaml" grafana-extra-allow-all
|
|
expect_fail 'an extra Grafana allow-all NetworkPolicy is rejected' \
|
|
assert_access_grafana_contract "$grafana" "$WORK/grafana-extra-policy.yaml"
|
|
|
|
mutate_yaml "$grafana" "$WORK/grafana-public-service.yaml" grafana-public-service
|
|
expect_fail 'Grafana public Service is rejected' assert_access_grafana_contract "$WORK/grafana-public-service.yaml" "$WORK/core-policies.yaml"
|
|
mutate_yaml "$grafana" "$WORK/grafana-missing-class.yaml" grafana-missing-class
|
|
expect_fail 'Grafana missing ingressClass is rejected' assert_access_grafana_contract "$WORK/grafana-missing-class.yaml" "$WORK/core-policies.yaml"
|
|
mutate_yaml "$grafana" "$WORK/grafana-main-token.yaml" grafana-main-token
|
|
expect_fail 'Grafana main container token mount is rejected' assert_access_grafana_contract "$WORK/grafana-main-token.yaml" "$WORK/core-policies.yaml"
|
|
mutate_yaml "$grafana" "$WORK/grafana-cluster-role.yaml" grafana-cluster-role
|
|
expect_fail 'cluster-wide dashboard ConfigMap RBAC is rejected' assert_access_grafana_contract "$WORK/grafana-cluster-role.yaml" "$WORK/core-policies.yaml"
|
|
|
|
for mutation in oidc-viewer-fallback oidc-editor oidc-grafana-admin oidc-group-missing; do
|
|
mutate_yaml "$grafana" "$WORK/$mutation.yaml" "$mutation"
|
|
expect_fail "Grafana OIDC regression $mutation is rejected" \
|
|
assert_access_grafana_contract "$WORK/$mutation.yaml" "$WORK/core-policies.yaml"
|
|
done
|
|
|
|
sed 's/@sha256:[0-9a-f]\{64\}//g' "$grafana" >"$WORK/grafana-tag-only.yaml"
|
|
expect_fail 'tag-only Grafana and sidecar images are rejected' assert_pinned_images "$WORK/grafana-tag-only.yaml"
|
|
|
|
expect_pass 'Blackbox fixed target and network contract' assert_access_blackbox_contract "$blackbox"
|
|
expect_pass 'Blackbox image is digest pinned' assert_pinned_images "$blackbox"
|
|
mutate_yaml "$blackbox" "$WORK/blackbox-broad-egress.yaml" blackbox-broad-egress
|
|
expect_fail 'broad Blackbox egress NetworkPolicy is rejected' \
|
|
assert_access_blackbox_contract "$WORK/blackbox-broad-egress.yaml"
|
|
mutate_yaml "$blackbox" "$WORK/blackbox-extra-policy.yaml" blackbox-extra-allow-all
|
|
expect_fail 'an extra Blackbox allow-all NetworkPolicy is rejected' \
|
|
assert_access_blackbox_contract "$WORK/blackbox-extra-policy.yaml"
|
|
mutate_yaml "$blackbox" "$WORK/blackbox-arbitrary.yaml" blackbox-arbitrary-target
|
|
expect_fail 'arbitrary Blackbox target is rejected' assert_access_blackbox_contract "$WORK/blackbox-arbitrary.yaml"
|
|
mutate_yaml "$blackbox" "$WORK/blackbox-public.yaml" blackbox-public-service
|
|
expect_fail 'public Blackbox Service is rejected' assert_access_blackbox_contract "$WORK/blackbox-public.yaml"
|
|
sed 's/@sha256:[0-9a-f]\{64\}//g' "$blackbox" >"$WORK/blackbox-tag-only.yaml"
|
|
expect_fail 'tag-only Blackbox image is rejected' assert_pinned_images "$WORK/blackbox-tag-only.yaml"
|
|
|
|
expect_pass 'target object set and target scope contract' assert_access_targets_contract "$WORK/targets.yaml"
|
|
mutate_yaml "$WORK/targets.yaml" "$WORK/targets-broad-egress.yaml" targets-broad-egress
|
|
expect_fail 'broad Prometheus target egress NetworkPolicy is rejected' \
|
|
assert_access_targets_contract "$WORK/targets-broad-egress.yaml"
|
|
mutate_yaml "$WORK/targets.yaml" "$WORK/targets-extra-policy.yaml" targets-extra-allow-all
|
|
expect_fail 'an extra Prometheus allow-all NetworkPolicy is rejected' \
|
|
assert_access_targets_contract "$WORK/targets-extra-policy.yaml"
|
|
expect_pass 'dashboard/rule/alertmanager contract' assert_access_rules_alerts_contract \
|
|
"$WORK/dashboards.yaml" "$WORK/rules.yaml" "$WORK/alertmanager.yaml"
|
|
alertmanager_source_before="$(publication_tree_fingerprint "$WORK/alertmanager.yaml")"
|
|
mutate_yaml "$WORK/alertmanager.yaml" "$WORK/alertmanager-missing-title.yaml" alertmanager-missing-title
|
|
expect_fail 'Alertmanager Slack config without title is rejected' \
|
|
assert_access_rules_alerts_contract \
|
|
"$WORK/dashboards.yaml" "$WORK/rules.yaml" "$WORK/alertmanager-missing-title.yaml"
|
|
expect_output 'rejected Alertmanager handoff preserves its canonical source exactly' \
|
|
"$alertmanager_source_before" publication_tree_fingerprint "$WORK/alertmanager.yaml"
|
|
mutate_yaml "$WORK/alertmanager.yaml" "$WORK/alertmanager-extra-policy.yaml" alertmanager-extra-allow-all
|
|
expect_fail 'an extra Alertmanager allow-all NetworkPolicy is rejected' \
|
|
assert_access_rules_alerts_contract \
|
|
"$WORK/dashboards.yaml" "$WORK/rules.yaml" "$WORK/alertmanager-extra-policy.yaml"
|
|
expect_pass 'all monitor/rule instance labels are scoped' assert_access_scope_contract \
|
|
"$blackbox" "$WORK/targets.yaml" "$WORK/rules.yaml"
|
|
|
|
mutate_yaml "$blackbox" "$WORK/missing-instance.yaml" missing-instance-label
|
|
expect_fail 'missing Probe instance label is rejected' assert_access_scope_contract "$WORK/missing-instance.yaml"
|
|
mutate_yaml "$blackbox" "$WORK/outside-target.yaml" outside-target
|
|
expect_fail 'out-of-scope platform target is rejected' assert_access_scope_contract "$WORK/outside-target.yaml"
|
|
mutate_yaml "$WORK/rules.yaml" "$WORK/forbidden-product.yaml" forbidden-product
|
|
expect_fail 'Spring/JVM/Kafka/batch/backup scope is rejected' assert_access_scope_contract "$WORK/forbidden-product.yaml"
|
|
mutate_yaml "$WORK/alertmanager.yaml" "$WORK/inline-secret.yaml" alertmanager-inline-secret
|
|
expect_fail 'inline credential Secret is rejected' assert_no_credentials "$WORK/inline-secret.yaml"
|
|
|
|
expect_pass 'private DNS is exact and private-only' assert_access_private_dns_contract \
|
|
"$WORK/private-dns.yaml" \
|
|
"$ROOT/infrastructure/networking/private-dns/host/dnsmasq-lan.conf" \
|
|
"$ROOT/infrastructure/networking/private-dns/host/dnsmasq-tailscale.conf"
|
|
mutate_yaml "$WORK/private-dns.yaml" "$WORK/private-dns-public.yaml" dns-public-address
|
|
expect_fail 'public Grafana DNS address assumption is rejected' assert_access_private_dns_contract \
|
|
"$WORK/private-dns-public.yaml" \
|
|
"$ROOT/infrastructure/networking/private-dns/host/dnsmasq-lan.conf" \
|
|
"$ROOT/infrastructure/networking/private-dns/host/dnsmasq-tailscale.conf"
|
|
|
|
expect_pass 'Traefik trust overlay render is accepted' assert_access_traefik_trust_contract "$WORK/traefik-trust.yaml"
|
|
expect_fail 'Traefik observe overlay render is rejected' assert_access_traefik_trust_contract "$WORK/traefik-observe.yaml"
|
|
|
|
make_publish_root
|
|
inventory_root=$PUBLISH_ROOT
|
|
expect_pass 'both verified inventory phases are accepted' validate_access_inventory_root "$inventory_root"
|
|
|
|
make_publish_root
|
|
inventory_unsafe_root=$PUBLISH_ROOT
|
|
chmod 0777 "$inventory_unsafe_root"
|
|
expect_fail 'world-writable inventory root is rejected' validate_access_inventory_root "$inventory_unsafe_root"
|
|
|
|
make_publish_root
|
|
inventory_unsafe_phase=$PUBLISH_ROOT
|
|
chmod 0755 "$inventory_unsafe_phase/post-substrate"
|
|
expect_fail 'group-readable inventory phase is rejected' validate_access_inventory_root "$inventory_unsafe_phase"
|
|
|
|
make_publish_root
|
|
inventory_unsafe_file=$PUBLISH_ROOT
|
|
chmod 0644 "$inventory_unsafe_file/post-substrate/inventory.json"
|
|
expect_fail 'group-readable inventory file is rejected' validate_access_inventory_root "$inventory_unsafe_file"
|
|
|
|
make_publish_root
|
|
inventory_hardlink_file=$PUBLISH_ROOT
|
|
mv -T "$inventory_hardlink_file/post-substrate/inventory.json" "$WORK/shared-inventory.json"
|
|
ln "$WORK/shared-inventory.json" "$inventory_hardlink_file/post-substrate/inventory.json"
|
|
expect_fail 'hard-linked inventory file is rejected' validate_access_inventory_root "$inventory_hardlink_file"
|
|
|
|
make_publish_root
|
|
inventory_bad_checksum=$PUBLISH_ROOT
|
|
printf '%064d inventory.json\n' 0 >"$inventory_bad_checksum/post-substrate/inventory.sha256"
|
|
chmod 0600 "$inventory_bad_checksum/post-substrate/inventory.sha256"
|
|
expect_fail 'inventory checksum drift is rejected' validate_access_inventory_root "$inventory_bad_checksum"
|
|
|
|
make_publish_root
|
|
inventory_missing_phase=$PUBLISH_ROOT
|
|
mv -T "$inventory_missing_phase/post-substrate" "$WORK/inventory-missing-phase-saved"
|
|
expect_fail 'missing post-substrate phase is rejected' validate_access_inventory_root "$inventory_missing_phase"
|
|
|
|
expect_pass 'rules-alerts component requires inventories' access_component_requires_inventory rules-alerts
|
|
expect_pass 'complete component requires inventories' access_component_requires_inventory complete
|
|
expect_fail 'grafana component does not accept inventory as a substitute for its source' access_component_requires_inventory grafana
|
|
|
|
expect_pass 'render context creates private files by default' \
|
|
secure_render_context_creates_private_files "$WORK"
|
|
|
|
publish_source="$WORK/publish-source"
|
|
mkdir -m 0700 -- "$publish_source"
|
|
printf '%s\n' 'kind: ConfigMap' >"$publish_source/dashboards.yaml"
|
|
printf '%s\n' 'kind: PrometheusRule' >"$publish_source/rules.yaml"
|
|
printf '%s\n' 'kind: AlertmanagerConfig' >"$publish_source/alertmanager.yaml"
|
|
chmod 0600 "$publish_source"/*.yaml
|
|
|
|
make_publish_root
|
|
publish_success_root=$PUBLISH_ROOT
|
|
expect_pass 'rules-alerts publication is complete and exact' \
|
|
publish_access_outputs "$publish_success_root" "$publish_source" rules-alerts
|
|
expect_pass 'rules-alerts publication preserves exact bytes and modes' \
|
|
published_rules_alerts_exact "$publish_success_root" "$publish_source"
|
|
|
|
complete_publish_source="$WORK/complete-publish-source"
|
|
mkdir -m 0700 -- "$complete_publish_source"
|
|
for complete_name in \
|
|
grafana.yaml blackbox.yaml targets.yaml dashboards.yaml rules.yaml alertmanager.yaml private-dns.yaml; do
|
|
cp --no-dereference --reflink=never -- \
|
|
"$WORK/$complete_name" "$complete_publish_source/$complete_name"
|
|
done
|
|
chmod 0600 "$complete_publish_source"/*.yaml
|
|
|
|
published_root_before="$(publication_tree_fingerprint "$publish_success_root")"
|
|
complete_source_before="$(publication_tree_fingerprint "$complete_publish_source")"
|
|
expect_fail 'a rules-alerts-published root rejects later complete publication' \
|
|
publish_access_outputs "$publish_success_root" "$complete_publish_source" complete
|
|
expect_output 'rejected complete publication preserves the published root exactly' \
|
|
"$published_root_before" publication_tree_fingerprint "$publish_success_root"
|
|
expect_output 'rejected complete publication preserves all complete sources exactly' \
|
|
"$complete_source_before" publication_tree_fingerprint "$complete_publish_source"
|
|
|
|
make_inventory_only_clone "$publish_success_root"
|
|
complete_publish_root=$PUBLISH_ROOT
|
|
target_inventory_before="$(publication_tree_fingerprint "$complete_publish_root/target-initial")"
|
|
post_inventory_before="$(publication_tree_fingerprint "$complete_publish_root/post-substrate")"
|
|
expect_pass 'the inventory-only clone exactly copies the four published inventory files' \
|
|
inventory_clone_matches_source "$publish_success_root" "$complete_publish_root"
|
|
expect_pass 'an inventory-only clone passes inventory validation' \
|
|
validate_access_inventory_root "$complete_publish_root"
|
|
expect_pass 'an inventory-only clone accepts complete publication' \
|
|
publish_access_outputs "$complete_publish_root" "$complete_publish_source" complete
|
|
expect_pass 'complete publication has the exact nine-entry topology and seven secure YAML files' \
|
|
published_complete_exact "$complete_publish_root" "$complete_publish_source"
|
|
expect_output 'complete publication preserves target-initial inventory exactly' \
|
|
"$target_inventory_before" publication_tree_fingerprint "$complete_publish_root/target-initial"
|
|
expect_output 'complete publication preserves post-substrate inventory exactly' \
|
|
"$post_inventory_before" publication_tree_fingerprint "$complete_publish_root/post-substrate"
|
|
expect_output 'complete publication preserves the rules-alerts source root exactly' \
|
|
"$published_root_before" publication_tree_fingerprint "$publish_success_root"
|
|
expect_output 'complete publication preserves all complete sources exactly' \
|
|
"$complete_source_before" publication_tree_fingerprint "$complete_publish_source"
|
|
|
|
make_inventory_only_clone "$publish_success_root"
|
|
complete_collision_root=$PUBLISH_ROOT
|
|
printf '%s\n' 'preexisting complete collision' >"$complete_collision_root/private-dns.yaml"
|
|
chmod 0600 "$complete_collision_root/private-dns.yaml"
|
|
complete_collision_before="$(publication_tree_fingerprint "$complete_collision_root")"
|
|
expect_fail 'a last-name collision rejects complete publication before any output is added' \
|
|
publish_access_outputs "$complete_collision_root" "$complete_publish_source" complete
|
|
expect_pass 'a complete last-name collision leaves no partial publication' \
|
|
complete_collision_left_no_partial "$complete_collision_root"
|
|
expect_output 'a rejected complete collision preserves its destination tree exactly' \
|
|
"$complete_collision_before" publication_tree_fingerprint "$complete_collision_root"
|
|
expect_output 'a rejected complete collision preserves the rules-alerts source root exactly' \
|
|
"$published_root_before" publication_tree_fingerprint "$publish_success_root"
|
|
expect_output 'a rejected complete collision preserves all complete sources exactly' \
|
|
"$complete_source_before" publication_tree_fingerprint "$complete_publish_source"
|
|
|
|
make_publish_root
|
|
publish_collision_root=$PUBLISH_ROOT
|
|
printf '%s\n' 'preexisting' >"$publish_collision_root/rules.yaml"
|
|
chmod 0600 "$publish_collision_root/rules.yaml"
|
|
expect_fail 'a later-name collision rejects the whole publication' \
|
|
publish_access_outputs "$publish_collision_root" "$publish_source" rules-alerts
|
|
expect_pass 'a later-name collision leaves no partial publication' \
|
|
collision_left_no_partial_publish "$publish_collision_root"
|
|
|
|
printf 'Assertions: %d\n' "$ASSERTIONS"
|
|
printf 'OBSERVABILITY ACCESS RENDER ASSERTION TEST PASS\n'
|