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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ git clone https://github.com/Gerrrt/HomeLab.git && cd HomeLab
make secrets-init # generate an age keypair, create the encrypted secrets file
make secrets-edit # fill in real values
make certs ARGS=--ca # create the lab CA
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20" # Grafana's leaf
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20 --dns grafana" # Grafana's leaf
make validate # everything CI runs
make up # render config and start the stack
```
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ graph LR
PROM[("Prometheus<br/>:9090 · 30d")]
LOKI[("Loki<br/>:3100 · 30d")]
AM["Alertmanager<br/>:9093"]
GRAF["Grafana<br/>:3000"]
GRAF["Grafana<br/>:3000 · https"]
end

OUT([Webhook<br/>notification])
Expand Down Expand Up @@ -175,7 +175,7 @@ re-shard of everything. Reasoning in

| Service | Port | Bound to | Notes |
| --- | --- | --- | --- |
| Grafana | 3000 | `${BIND_ADDR}` | The only UI meant to be opened by a human, and the only published service that authenticates |
| Grafana | 3000 | `${BIND_ADDR}` | The only UI meant to be opened by a human, and the only service that terminates TLS or authenticates — `https://`, on a lab-CA certificate a browser will warn about until you trust `certificates/ca.pem` |
| Prometheus | 9090 | `${BIND_ADDR}` | Remote-write receiver — `oracle`'s agent pushes here. Unauthenticated; see [`security.md`](security.md) |
| Loki | 3100 | `${BIND_ADDR}` | Push endpoint — `oracle`'s agent pushes here. Unauthenticated; see [`security.md`](security.md) |
| Alertmanager | 9093 | `127.0.0.1` | Nothing off-host uses it; silences are reached through Grafana |
Expand Down
2 changes: 1 addition & 1 deletion docs/runbooks/deploy-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ make secrets-edit # replace every change-me value
# TLS. Grafana serves https from this leaf and Prometheus verifies it with the
# CA — see generate-certificates.md. Both are required before the stack starts.
make certs ARGS=--ca
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20"
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20 --dns grafana"

make validate # confirm the configs are sound before starting anything
make up
Expand Down
30 changes: 21 additions & 9 deletions docs/runbooks/generate-certificates.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ that rewrite as compromised, including the CA. What follows replaces it.
> [`deploy-stack.md`](deploy-stack.md), not a someday step. `make render`
> refuses to run until they exist, because before it did, `make up` bind-mounted
> the absent files and Docker created directories in their place
> ([#69](https://github.com/Gerrrt/HomeLab/issues/69)). Prometheus, Alertmanager
> and Loki still publish plain HTTP on the management VLAN; putting the rest
> behind TLS is tracked in [`roadmap.md`](../roadmap.md).
> ([#69](https://github.com/Gerrrt/HomeLab/issues/69)). Grafana is the only
> service that terminates TLS. Prometheus, Alertmanager and Loki publish plain
> HTTP on the management VLAN, and that is an accepted residual with firewall
> default-deny as the control — not work in progress; see
> [`security.md`](../security.md).

## Where things live

Expand Down Expand Up @@ -59,14 +61,22 @@ trust store holding it, so that has to be deliberate (`--force`).
## 2. Issue a leaf

```bash
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20"
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20 --dns grafana"
```

Include `--ip` for anything reached by address. `docs/roadmap.md` still lists
internal DNS as unresolved, so in practice most services here are reached by IP,
and a certificate without a matching IP SAN will be rejected at the point you
most want it to work.

`--dns grafana` is not decoration. Prometheus scrapes the `grafana` job over the
compose network, where the service answers to its short name, so
[`prometheus.yaml`](../../stacks/observability/prometheus/prometheus.yaml) sets
`server_name: grafana` and verifies that name against the leaf. Issue this
certificate with the FQDN alone and Grafana still serves fine in a browser while
the scrape fails with `x509: certificate is valid for grafana.matrix.elysium,
not grafana` — one target down, for a reason that reads like a trust problem.

Two limits the script enforces rather than lets you discover later:

- **A certificate with no `subjectAltName` is rejected outright** by every
Expand Down Expand Up @@ -110,15 +120,17 @@ lab with one certificate is better served by a calendar reminder than by a
renewal daemon nobody maintains.

```bash
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20 --force"
make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20 --dns grafana --force"
```

Then restart whatever serves it. The CA does not change, so nothing needs
re-trusting.

Adding blackbox-exporter for TLS-expiry checks is on
[`roadmap.md`](../roadmap.md); until that exists, expiry is something you find
out about from a browser warning.
blackbox-exporter exists and could carry this, but does not yet: it probes only
the wiki, over plain HTTP, and nothing reads `probe_ssl_earliest_cert_expiry`.
Grafana is not a target, so nothing watches the one certificate this runbook
issues — expiry is still something you find out about from a browser warning.
Tracked in [#91](https://github.com/Gerrrt/HomeLab/issues/91).

## If something goes wrong

Expand Down Expand Up @@ -146,7 +158,7 @@ certificate with `-s`, and `-s` is true of a directory:
```console
$ ls -ld certificates/grafana.matrix.elysium-key.pem
drwxr-xr-x 2 you you 4096 ... certificates/grafana.matrix.elysium-key.pem
$ make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20"
$ make certs ARGS="--host grafana.matrix.elysium --ip 10.0.99.20 --dns grafana"
error: certificates/grafana.matrix.elysium-key.pem already exists — pass --force to replace it
```

Expand Down
18 changes: 12 additions & 6 deletions scripts/gen-certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
# The control that matters is not the file mode, it is that these never become
# tracked files. `make validate` asserts that; so does CI.
#
# Nothing in this repository terminates TLS yet. This exists so that when
# something does — Grafana is the first candidate, see docs/roadmap.md — issuing
# a certificate is one command rather than a research project, and so the CA is
# created deliberately rather than improvised at the point of need.
# Grafana terminates TLS with a leaf issued here, and Prometheus verifies that
# certificate against this CA when it scrapes the grafana job — so both are a
# prerequisite of `make up` rather than groundwork for a someday service. What
# this script buys is that issuing one stays a single command rather than a
# research project, and that the CA is created deliberately rather than
# improvised at the point of need.
#
# Usage:
# scripts/gen-certs.sh --ca create the CA (refuses if it exists)
Expand Down Expand Up @@ -66,7 +68,11 @@ while (($#)); do
--dns) DNS+=("${2:?--dns needs a name}"); shift 2 ;;
--days) LEAF_DAYS="${2:?--days needs a number}"; shift 2 ;;
--force) FORCE=1; shift ;;
-h|--help) sed -n '2,32p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
# Printed straight from the comment block above rather than kept as a
# second copy that drifts. Bounded by where the comments stop, not by a
# line number: the literal 2,32p this replaced had already slipped and was
# truncating the last paragraph mid-sentence.
-h|--help) awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "${BASH_SOURCE[0]}"; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
Expand Down Expand Up @@ -127,7 +133,7 @@ that holds it. Pass --force only if that is what you mean."
ok "CA created"
printf ' key %s (never commit, never copy off this host)\n' "${CA_KEY#"${REPO_ROOT}"/}"
printf ' cert %s (safe to distribute — this is what clients trust)\n' "${CA_CRT#"${REPO_ROOT}"/}"
printf '\nIssue a leaf with:\n scripts/gen-certs.sh --host grafana.matrix.elysium --ip 10.0.99.20\n\n'
printf '\nIssue a leaf with:\n scripts/gen-certs.sh --host grafana.matrix.elysium --ip 10.0.99.20 --dns grafana\n\n'
exit 0
fi

Expand Down
2 changes: 1 addition & 1 deletion scripts/render-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Grafana serves https from the leaf and Prometheus verifies it with the CA, so
the stack cannot start without them. Generate them with:

make certs ARGS=--ca
make certs ARGS=\"--host grafana.matrix.elysium --ip 10.0.99.20\"
make certs ARGS=\"--host grafana.matrix.elysium --ip 10.0.99.20 --dns grafana\"

Full procedure in docs/runbooks/generate-certificates.md."
fi
Expand Down
2 changes: 1 addition & 1 deletion stacks/observability/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ make up # from the repository root
| `prometheus` | `prom/prometheus` | 9090 | Metrics store, remote-write receiver, rule evaluation |
| `alertmanager` | `prom/alertmanager` | 9093 (localhost) | Alert routing, grouping, inhibition |
| `loki` | `grafana/loki` | 3100 | Log store |
| `grafana` | `grafana/grafana-oss` | 3000 | Dashboards — the only published UI, and the only one that authenticates |
| `grafana` | `grafana/grafana-oss` | 3000 (https) | Dashboards — the only published UI, and the only service that terminates TLS or authenticates |
| `snmp-exporter` | `prom/snmp-exporter` | *internal* | SNMP polling proxy |
| `alloy` | `grafana/alloy` | 12345 (localhost) | Metric and log collection |

Expand Down