Secure Spring Boot actuator endpoints

Rationale TL;DR

Spring Boot Actuator is a artifact that provides the ability to monitoring our app, gathering metrics, understanding traffic or the state of our database.

Needed dependencies

To pull in Spring Boot actuator, we simply need to add the spring-boot-starter-actuator dependency. If we are using maven:

    <dependencies>
    ...
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    ...
    </dependencies>

Endpoints published

Actuator provides a set of endpoints to gather info from or even interact with the application. Some of them are sensitive, which means that they are not fully public and thus they will omitt some information.

We can set which endpoints publish. We can tell actuator not to publish any endpoint but a list of endpoints:

endpoints.enabled: false
endpoints.metrics.enabled: true
endpoints.metrics.sensitive: false
endpoints.prometheus.enabled: true
endpoints.prometheus.sensitive: false
endpoints.health.enabled: true
endpoints.health.sensitive: false

In this example, only metrics, prometheus and health are published by actuator.

To provide security to Actuator 1.X we can use the following on our application.properties file:

management.security.enabled=true

Bear in mind that Actuator 1.X will apply its own security model. So that we need to tell Spring Boot Security to not apply its rules to all endpoints:

security.basic.enabled: false

Also, we can configure a user and actuator will pick it up and use it to grant access to its endpoints:

security:
  user:
    name: admin
    password:  admin

That way, we can use basic authentication with the actuator endpoints but the app endpoints will not use Spring Boot Security:

─  $ ▶ curl -XGET -vv -u 'admin:admin' http://localhost:8080/app/metrics
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'admin'
> GET /app/metrics HTTP/1.1
> Host: localhost:8080
> Authorization: Basic Z3Vlc3RtYXRlLWFwcDpndWVzdG1hdGUtYXBw
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
< X-Application-Context: app:local
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Strict-Transport-Security: max-age=31536000 ; includeSubDomains
< Content-Type: application/vnd.spring-boot.actuator.v1+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 02 Jul 2019 06:32:52 GMT
<
* Connection #0 to host localhost left intact
{"mem":1649575,"mem.free":1248587,"processors":4,"instance.uptime":200403,"uptime":288597,"systemload.average":2.73828125,"heap.committed":1535488,"heap.init":262144,"heap.used":286900,"heap":3728384,"nonheap.committed":118784,"nonheap.init":2496,"nonheap.used":114087,"nonheap":0,"threads.peak":47,"threads.daemon":41,"threads.totalStarted":57,"threads":44,"classes":13275,"classes.loaded":13275,"classes.unloaded":0,"gc.ps_scavenge.count":14,"gc.ps_scavenge.time":532,"gc.ps_marksweep.count":3,"gc.ps_marksweep.time":506,"datasource.primary.active":0,"datasource.primary.usage":0.0,"gauge.response.health":17.0,"gauge.response.metrics":24.0,"gauge.response.prometheus":39.0,"gauge.response.star-star":20.0,"counter.status.200.metrics":1,"counter.status.401.health":1,"counter.status.404.star-star":4,"counter.status.503.health":3,"counter.status.200.prometheus":1

We can also use a different port to expose the actuator. This only works if we’re using a embedded webserver, using a standalone application server will not work because the app will not be able to bind to a different port than the ports configured by the application server:

management.port: 8081

For Actuator 2.X, there are a lot of changes. Will dig into it in a future post.