Skip to content

feat: actuator health on all apps; other endpoints require an admin token - #240

Merged
dfcoffin merged 1 commit into
mainfrom
feat/actuator-health-admin-gate
Nov 14, 2026
Merged

dfcoffin merged 1 commit into
mainfrom
feat/actuator-health-admin-gate

Conversation

@dfcoffin

Copy link
Copy Markdown
Contributor

Summary

Adds Spring Boot Actuator health to all three applications and limits anonymous access to endpoints that only report normal operation. Closes #236.

DC AS TP
/actuator/health, /health/liveness, /health/readiness anonymous, status only anonymous, status only anonymous, status only
/actuator, info, metrics (+ prometheus on DC/TP) DataCustodian_Admin_Access token authserver.admin token token with espi.actuator.admin-scope (default ThirdParty_Admin_Access), introspected at the AS
env not exposed no longer exposed not exposed

Why DC's health was 404

openespi-common ships application.properties (and profile ymls) inside its jar, and every dependent app loads it. It set management.endpoints.enabled-by-default=false, which is deprecated but still honored in Boot 4.1.2, so every endpoint was disabled. The same file's spring.application.name=OpenESPI-Common overrode each app's own name (a .properties file outranks .yml), so all DC/TP metrics were tagged application="OpenESPI-Common". Both settings are removed from common.

Other changes

  • Endpoint access is opt-in (management.endpoints.access.default: none) and read-only per endpoint.
  • AS profiles drop enabled-by-default / endpoint.*.enabled, which Boot rejects alongside the access keys.
  • AS test profiles no longer override the exposure, so tests exercise the real rules.
  • DC/TP local profiles now use the client secrets the AS seeds (dc-secret, tp-secret). With secret, every token introspection failed with 401 from the AS, which broke DC's ESPI API locally as well.

Test plan

  • New ActuatorSecurityConfigurationTest (DC): health + probes anonymous with no components; /actuator, info, metrics → 401 without a token, 403 with a non-admin token, 200 with an admin token
  • New cases in AdminBearerTokenIntegrationTest (AS, testcontainers-it): health anonymous; /actuator, info, metrics → 401 / 403 (DC admin token) / 200 (authserver.admin). Not run locally (no Docker on Windows); CI runs them.
  • mvn verify DC: 172 tests pass; TP: 44 pass (2 skipped, pre-existing)
  • Live, all three apps on the local profile: health/liveness/readiness 200 anonymous; every other endpoint 401 with no token or a bogus token, 403 with the wrong admin token, 200 with the right one (TP checked with the scope overridden to DataCustodian_Admin_Access, because no seeded AS client can obtain ThirdParty_Admin_Access by client credentials)

Notes for reviewers

  • If the AS is unreachable during introspection, DC/TP answer 500 (Spring's standard behaviour for AuthenticationServiceException), not 401.
  • info is now admin-only on DC and AS (it was anonymous).

🤖 Generated with Claude Code

…n token

- openespi-common no longer ships management.* or spring.application.name in its
  jar's application.properties / profile ymls. Every dependent app loaded them:
  management.endpoints.enabled-by-default=false (deprecated, still honored by
  Boot 4.1.2) disabled all endpoints, so DC's /actuator/health returned 404, and
  the .properties name overrode each app's own name in metric tags.
- Each app: endpoint access is opt-in (access.default: none) and read-only.
  Exposed: health, info, metrics (+ prometheus on DC/TP). health shows status
  only and enables the liveness/readiness probe groups. AS no longer exposes env.
- Each app gets an actuator SecurityFilterChain (EndpointRequest): health is
  anonymous; everything else requires an admin access token.
  - DC: opaque token with DataCustodian_Admin_Access (existing introspector).
  - AS: authserver.admin bearer token, resolved in-process (as for #204).
  - TP: opaque token introspected at the AS with the TP's client credentials,
    scope espi.actuator.admin-scope (default ThirdParty_Admin_Access).
- AS adds spring-boot-starter-actuator; TP adds the resource-server starter.
- AS profiles drop enabled-by-default / endpoint.*.enabled, which Boot rejects
  next to the access keys.
- DC/TP local profiles use the secrets the AS seeds (dc-secret, tp-secret);
  with "secret" every introspection got a 401 from the AS.

Closes #236

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dfcoffin
dfcoffin merged commit a78e8b6 into main Nov 14, 2026
11 checks passed
@dfcoffin
dfcoffin deleted the feat/actuator-health-admin-gate branch November 14, 2026 16:48
dfcoffin added a commit that referenced this pull request Nov 14, 2026
…framework versions (#248)

- Third Party: new public /about (AboutController + about.html on the shared
  GBA layout), linked from the footer, permitted anonymously.
- Data Custodian and Third Party About pages now show the same fields:
  implementation, version, build time, the standard (NAESB REQ.21 ESPI 4.1)
  and a release status derived from the version (a SNAPSHOT is "Pre-release:
  not for production use"), plus links to GBA, the API docs, the source and
  SECURITY.md.
- The Java and Spring Boot versions are no longer shown to anonymous
  visitors: exact framework versions help attackers pick known CVEs, and they
  stay available to admins via /actuator/info (#240).
- Version and build time come from BuildProperties
  (spring-boot-maven-plugin:build-info, added to both apps) instead of the jar
  manifest, which is absent under spring-boot:run ("(development build)").
- Removed the legacy VersionRESTController (/about-version) from both apps.
  It read a servlet-context MANIFEST.MF that a Spring Boot jar does not have,
  and it filled a model key the DC template never read (DC) or rendered a
  template that did not exist (TP).
- Tests: AboutControllerTest in both apps. GbaLayoutPagesTest now renders
  under the real /ThirdParty context path and asserts that every internal
  href/src carries it. With MockMvc's empty context path, a correct
  th:href="@{/about}" and a broken href="/about" rendered identically, so the
  previous #235 guard could not catch the bug.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
dfcoffin added a commit that referenced this pull request Nov 14, 2026
AuthorizationServerApplicationTests pins the exact set of SecurityFilterChain
beans. It was written before #240 added actuatorSecurityFilterChain, so the
first CI run of the rebased suite failed on that one assertion (1 of 274).
Keep the pin (a new chain changes which requests the others see) and list the
fifth chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfcoffin added a commit that referenced this pull request Nov 14, 2026
* test(#129): drop impossible security assertions from OAuthAdminControllerTest

The standalone MockMvc setup installs no security filter chain and no
method-security proxy, so the 401/403 (and "allowed") tests could never
exercise access control. Admin API authorization is covered against the
real configuration by AdminEndpointAuthorizationIntegrationTest and
AdminBearerTokenIntegrationTest. The request-handling tests stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(#129): drive EspiTokenCustomizerTest with a real JwtEncodingContext

Replace the strict-stubbed JwtEncodingContext mocks (UnnecessaryStubbing,
NPE on null claims) with a real context and assertions on the resulting
claims. Role-to-resource-URI and customer-grant cases become parameterized;
duplicate "ESPI compliance" cases and null-context/null-claims/null-endpoint
cases (outside the framework contract) are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(#129): replace mock-HttpSecurity config test with a real context test

AuthorizationServerConfigTest called the @bean filter-chain methods against
a mocked HttpSecurity and stubbed JdbcTemplate (ClassCast, UnfinishedStubbing,
null @value settings, removed /connect/register and JWK expectations) - it
tested the framework, not our configuration. AuthorizationServerApplicationTests
booted on the H2 'local' profile, which no longer starts.

AuthorizationServerApplicationTests now extends AbstractAuthserverIntegrationTest
(real config, MySQL Testcontainer) and asserts what the started context holds:
the four security filter chains, no JwtDecoder/JwtEncoder (opaque only, #134),
issuer and protocol endpoints, the JDBC client repository, grant-context
authorization service and ESPI customizer beans, and the seeded default clients'
grants, scopes, token format, TTLs, consent and PKCE settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(#129): run SecurityIntegrationTest against the real config on MySQL

Move SecurityIntegrationTest off the H2 'test' profile (no schema: the H2
profile had no Flyway location) onto AbstractAuthserverIntegrationTest, and
correct its stale expectations:

- use the seeded clients' real secrets (the "{bcrypt}secret" literal never matched)
- malformed Basic header -> 400 invalid_request; client_secret_post on a
  client_secret_basic client -> 401 invalid_client
- protocol endpoints are CSRF-exempt by design; CSRF is asserted on the
  session-chain admin DELETE with an MFA-complete admin instead
- discovery document instead of the removed /oauth2/jwks endpoint
- authorize requests use queryParam (Spring AS reads the query string) and a
  per-test client, and assert delegation/redirect-URI/scope outcomes exactly
- introspection by the DataCustodian client of another client's token

Removed: the six admin access-control cases (covered by
AdminEndpointAuthorizationIntegrationTest / AdminBearerTokenIntegrationTest),
the duplicate opaque-token case, the no-op CORS case, and the unused
application-test.yml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(#129): run the full openespi-authserver suite in CI and releases

The authserver unit tests are repaired, so drop the -Dgroups=testcontainers-it
restriction from the CI test matrix and from release-meta.sh, and update
CLAUDE.md, README.md, RELEASING.md, CI_CD_SETUP.md and the testcontainers-it
tag comments to say that `mvn verify -pl openespi-authserver` runs everything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(#129): expect the actuator security filter chain added by #240

AuthorizationServerApplicationTests pins the exact set of SecurityFilterChain
beans. It was written before #240 added actuatorSecurityFilterChain, so the
first CI run of the rebased suite failed on that one assertion (1 of 274).
Keep the pin (a new chain changes which requests the others see) and list the
fifth chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DC: no Actuator health endpoint (/actuator/health 404) for container/orchestrator health checks

1 participant