Spring Boot 4 Actuator in production: endpoints, security, custom health indicators
Companion repository for the ankurm.com article. Every transcript in docs/output/ was produced by running this project; scripts/run-all.sh regenerates all of them. Verified against Spring Boot 4.1.1 / Framework 7.0.9 / Security 7.1.1 / Micrometer 1.17.1 / kafka-clients 4.2.1 on Temurin JDK 25.0.4.1+1.
This commit is contained in:
89
docs/03-endpoint-catalogue.md
Normal file
89
docs/03-endpoint-catalogue.md
Normal file
@@ -0,0 +1,89 @@
|
||||
[← 02 Boot 4 changes](02-boot-4-changes.md) · **03 · The endpoint catalogue** · [04 Securing Actuator →](04-securing-actuator.md)
|
||||
|
||||
# 03 — The endpoint catalogue
|
||||
|
||||
The table in the [README](../README.md#endpoints) is the reference. This chapter covers the
|
||||
three endpoints people get wrong.
|
||||
|
||||
## `env` does not leak values
|
||||
|
||||
The oldest Actuator scare story is "`/actuator/env` dumps your database password". In Spring
|
||||
Boot 4 it does not. From [`output/03-open-actuator-leak.txt`](output/03-open-actuator-leak.txt),
|
||||
with no credentials sent at all:
|
||||
|
||||
```json
|
||||
{"source":"Config resource 'class path resource [application.yaml]' ...","value":"******"}
|
||||
```
|
||||
|
||||
That is `spring.datasource.password`, and it is also `acme.partner.credential` — a property
|
||||
whose name matches none of the classic `password`/`secret`/`token`/`key` patterns. Both masked.
|
||||
|
||||
Masking is not pattern matching on key names. It is `management.endpoint.env.show-values`,
|
||||
which defaults to `never`. Set it to `always` and both come back in plaintext:
|
||||
|
||||
```
|
||||
acme.partner.credential S3CRET-partner-credential
|
||||
spring.datasource.password not-a-real-password-but-watch-what-/actuator/env-does-with-it
|
||||
spring.security.user.password ops-password
|
||||
```
|
||||
|
||||
So the risk with `env` is real but it is a *configuration* risk, not a default. The `origin`
|
||||
field is also worth knowing about — it reports the exact file and line a property came from,
|
||||
which is genuinely the fastest way to answer "where is this value coming from" in a service
|
||||
with six property sources.
|
||||
|
||||
## `heapdump` is not exposed by `include: "*"`
|
||||
|
||||
This surprises people who learned Actuator on Boot 2.
|
||||
|
||||
```
|
||||
$ curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/actuator/heapdump
|
||||
404
|
||||
```
|
||||
|
||||
…with `management.endpoints.web.exposure.include: "*"` and a `permitAll` security chain.
|
||||
`management.endpoint.heapdump.access` defaults to `none`, and exposure does not override
|
||||
access. Reading Spring Boot's own `spring-configuration-metadata.json` for every
|
||||
`management.endpoint.*.access` key gives exactly two endpoints defaulting to `none`:
|
||||
|
||||
| Endpoint | `access` default |
|
||||
|---|---|
|
||||
| `heapdump` | `none` |
|
||||
| `shutdown` | `none` |
|
||||
| everything else | `unrestricted` |
|
||||
|
||||
Turn it on and the story changes completely —
|
||||
[`output/04-heapdump-leak.txt`](output/04-heapdump-leak.txt):
|
||||
|
||||
```
|
||||
status=200 bytes=59065395 type=application/octet-stream
|
||||
|
||||
$ strings /tmp/heap.hprof | grep -c 'S3CRET-partner-credential'
|
||||
1
|
||||
$ strings /tmp/heap.hprof | grep -o 'not-a-real-password[^"]*' | head -1
|
||||
not-a-real-password-but-watch-what-/actuator/env-does-with-it
|
||||
```
|
||||
|
||||
59 MB containing, in plaintext, both properties that `/actuator/env` had just masked.
|
||||
Sanitisation is a rendering feature of one endpoint. It is not a security boundary.
|
||||
|
||||
## `loggers` is a write endpoint
|
||||
|
||||
`GET /actuator/loggers/{name}` is harmless. `POST` is not:
|
||||
|
||||
```
|
||||
$ curl -X POST -H 'Content-Type: application/json' \
|
||||
-d '{"configuredLevel":"TRACE"}' /actuator/loggers/org.springframework
|
||||
status=204
|
||||
```
|
||||
|
||||
204, no credentials. An attacker who can do that has a denial-of-service primitive (TRACE on a
|
||||
busy service will fill a disk) and, depending on your logging configuration, a way to get
|
||||
request bodies and headers written to a file they may be able to read through another channel.
|
||||
|
||||
`beans` (426 entries here) and `mappings` (30 servlet mappings) are read-only but are pure
|
||||
reconnaissance — they describe your entire application's shape.
|
||||
|
||||
---
|
||||
|
||||
[← 02](02-boot-4-changes.md) · **03** · [04 Securing Actuator →](04-securing-actuator.md)
|
||||
Reference in New Issue
Block a user