From 86f65591dcb23a86ebd9fcff3eeb98ce2aad4900 Mon Sep 17 00:00:00 2001 From: Weite Dai Date: Fri, 4 Sep 2026 17:18:11 +1000 Subject: [PATCH 1/2] fix: correct EC2 auth filter profile name to match real production profile --- Dockerfile | 2 +- .../Ec2InstanceAuthenticationFilter.java | 8 +++-- ...enticationFilterProductionProfileTest.java | 36 +++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/test/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilterProductionProfileTest.java diff --git a/Dockerfile b/Dockerfile index bdcfd43..c88b8bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # Dockerfile for Ocean Current API # Environment Variables Required: -# - SPRING_PROFILES_ACTIVE: Spring profile (prod, edge) +# - SPRING_PROFILES_ACTIVE: Spring profile (dev, edge, production) # - ES_HOST: Elasticsearch host # - ES_API_KEY: Elasticsearch API key # - REMOTE_BASE_URL: Remote Server base URL diff --git a/src/main/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilter.java b/src/main/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilter.java index c2854d4..0162582 100644 --- a/src/main/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilter.java +++ b/src/main/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilter.java @@ -21,11 +21,12 @@ import java.io.IOException; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; /** * Authentication filter for internal monitoring endpoints. - * Only active in production and edge environments. + * Only active in the "production" and "edge" profiles *

* Validates EC2 instance identity using: * 1. Instance identity document from EC2 metadata service @@ -38,7 +39,7 @@ * - Whitelist prevents unauthorised instances from accessing the endpoint */ @Component -@Profile({"prod", "edge"}) +@Profile({"production", "edge"}) @Slf4j @RequiredArgsConstructor public class Ec2InstanceAuthenticationFilter extends OncePerRequestFilter { @@ -53,7 +54,8 @@ public class Ec2InstanceAuthenticationFilter extends OncePerRequestFilter { @PostConstruct public void init() { - this.authorisedInstanceIds = new HashSet<>(monitoringSecurityProperties.getAuthorisedInstanceIds()); + List configuredIds = monitoringSecurityProperties.getAuthorisedInstanceIds(); + this.authorisedInstanceIds = configuredIds != null ? new HashSet<>(configuredIds) : Collections.emptySet(); log.info("Initialized EC2 authentication filter with {} authorised instance IDs", authorisedInstanceIds.size()); } diff --git a/src/test/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilterProductionProfileTest.java b/src/test/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilterProductionProfileTest.java new file mode 100644 index 0000000..03b41e5 --- /dev/null +++ b/src/test/java/au/org/aodn/oceancurrent/security/Ec2InstanceAuthenticationFilterProductionProfileTest.java @@ -0,0 +1,36 @@ +package au.org.aodn.oceancurrent.security; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles({"test", "production"}) +public class Ec2InstanceAuthenticationFilterProductionProfileTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void testMonitoringEndpoint_WithoutPkcs7_ReturnUnauthorised() throws Exception { + String requestBody = "{\"errorMessage\": \"Test error\"}"; + + mockMvc.perform(post("/api/v1/monitoring/fatal-log") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value("Unauthorized")) + .andExpect(jsonPath("$.errors[0]").value("PKCS7 signature required")); + } +} From 6914747d61893edf8b93fa68bd6d9dfd7936b6f8 Mon Sep 17 00:00:00 2001 From: Weite Dai Date: Fri, 4 Sep 2026 17:24:23 +1000 Subject: [PATCH 2/2] fix: correct "prod" profile references to "production" --- docs/EC2_AUTHENTICATION_GUIDE.md | 16 ++++++++-------- scripts/README.md | 2 +- scripts/test_local_auth.py | 12 ++++++------ .../configuration/OpenApiConfig.java | 2 +- .../configuration/SecurityConfig.java | 2 +- .../controller/MonitoringController.java | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/EC2_AUTHENTICATION_GUIDE.md b/docs/EC2_AUTHENTICATION_GUIDE.md index b07f94a..2ae27ce 100644 --- a/docs/EC2_AUTHENTICATION_GUIDE.md +++ b/docs/EC2_AUTHENTICATION_GUIDE.md @@ -99,7 +99,7 @@ Every EC2 instance has access to a special metadata service at `http://169.254.1 ### What the Backend Does -- **`Ec2InstanceAuthenticationFilter`** (prod/edge only) intercepts `/api/v1/monitoring/**`: +- **`Ec2InstanceAuthenticationFilter`** (production/edge only) intercepts `/api/v1/monitoring/**`: - Parses the JSON body into `MonitoringRequest` - Checks `pkcs7` is present @@ -113,7 +113,7 @@ Every EC2 instance has access to a special metadata service at `http://169.254.1 - **`MonitoringController`** then logs a `[FATAL]` message that monitoring systems (e.g. NewRelic) can pick up. -Authentication is **only enforced in `prod` and `edge` profiles**; in local/dev you can call the endpoint without EC2 metadata. +Authentication is **only enforced in `production` and `edge` profiles**; in local/dev you can call the endpoint without EC2 metadata. ## Security Features @@ -202,7 +202,7 @@ Multiple layers of security work together: 3. **Extraction**: Server extracts document from PKCS7 (prevents tampering) 4. **Timestamp**: Replay attack prevention 5. **Whitelist**: Instance-level access control -6. **Profile**: Only active in prod/edge environments +6. **Profile**: Only active in production/edge environments ## Architecture @@ -242,7 +242,7 @@ Multiple layers of security work together: - Check extracted instance ID against whitelist - Return 401 Unauthorized if validation fails -**Active Profiles**: `prod`, `edge` only (disabled in dev/test) +**Active Profiles**: `production`, `edge` only (disabled in dev/test) #### 3. MonitoringSecurityProperties @@ -325,7 +325,7 @@ The whitelisted instance IDs are configured via the `AUTHORISED_INSTANCE_IDS` en -Dapp.monitoring-security.authorised-instance-ids=${AUTHORISED_INSTANCE_IDS} ``` -2. **Configuration** in `application.yaml` / `application-prod.yaml` / `application-edge.yaml`: +2. **Configuration** in `application.yaml` / `application-production.yaml` / `application-edge.yaml`: ```yaml app: @@ -366,13 +366,13 @@ aws ec2 describe-instances \ ### 5. Active Profiles ```java -@Profile({"prod", "edge"}) +@Profile({"production", "edge"}) public class Ec2InstanceAuthenticationFilter extends OncePerRequestFilter { // ... } ``` -- **prod/edge**: full authentication (PKCS7 + whitelist) is enforced. +- **production/edge**: full authentication (PKCS7 + whitelist) is enforced. - **local/dev**: the filter is inactive; you can call the endpoint without EC2 metadata: ```bash @@ -661,7 +661,7 @@ logging: When requests fail, verify: -- [ ] Application is running with `prod` or `edge` profile +- [ ] Application is running with `production` or `edge` profile - [ ] Instance ID (extracted from PKCS7) is in `AUTHORISED_INSTANCE_IDS` - [ ] Certificate file exists at configured path - [ ] Request includes the required `pkcs7` field diff --git a/scripts/README.md b/scripts/README.md index 5240e54..bb23bbf 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -69,7 +69,7 @@ export OC_API_ENDPOINT="http://localhost:8080/api/v1/monitoring/fatal-log" python3 trigger_fatal_log.py "Test error from local" ``` -> In non‑prod profiles the EC2 authentication filter is disabled, so only the `errorMessage` is used. +> In non‑production profiles the EC2 authentication filter is disabled, so only the `errorMessage` is used. ## Additional docs diff --git a/scripts/test_local_auth.py b/scripts/test_local_auth.py index b8c9ac0..8c7385a 100755 --- a/scripts/test_local_auth.py +++ b/scripts/test_local_auth.py @@ -3,7 +3,7 @@ EC2 Instance Identity Authentication Test Tests EC2 instance identity PKCS7 signature validation. -Authentication filter is only active in edge and prod profiles. +Authentication filter is only active in edge and production profiles. Usage: # Start the app with edge profile (includes auth filter) @@ -19,7 +19,7 @@ pip3 install requests Note: - - Auth filter is only active with edge or prod profiles + - Auth filter is only active with edge or production profiles - Local testing requires SPRING_PROFILES_ACTIVE=edge ./gradlew bootRun - EC2 identity validation requires real PKCS7 signatures from EC2 metadata service """ @@ -51,7 +51,7 @@ def fetch_ec2_identity(use_ec2_metadata=False): print("ℹ️ PKCS7 signatures must be real and cryptographically valid") print(" To test EC2 identity validation:") print(" - Run on actual EC2 instance with --use-ec2-metadata flag") - print(" - Authentication filter is only active in edge/prod profiles") + print(" - Authentication filter is only active in edge/production profiles") return None print("ℹ️ Fetching EC2 identity from metadata service...") @@ -83,7 +83,7 @@ def fetch_ec2_identity(use_ec2_metadata=False): def test_without_auth(): - """Test endpoint without authentication (should be rejected on edge/prod).""" + """Test endpoint without authentication (should be rejected on edge/production).""" print("\n1. Testing without authentication:") try: @@ -95,11 +95,11 @@ def test_without_auth(): print(f" Status: {response.status_code}") if response.status_code == 401: - print(" ✅ Authentication is enforced (expected on edge/prod)") + print(" ✅ Authentication is enforced (expected on edge/production)") return True else: print(f" ❌ Expected 401 but got {response.status_code}") - print(" Make sure app is running with edge or prod profile") + print(" Make sure app is running with edge or production profile") print(" Example: SPRING_PROFILES_ACTIVE=edge ./gradlew bootRun") return False diff --git a/src/main/java/au/org/aodn/oceancurrent/configuration/OpenApiConfig.java b/src/main/java/au/org/aodn/oceancurrent/configuration/OpenApiConfig.java index 38808b0..d8a2e23 100644 --- a/src/main/java/au/org/aodn/oceancurrent/configuration/OpenApiConfig.java +++ b/src/main/java/au/org/aodn/oceancurrent/configuration/OpenApiConfig.java @@ -11,7 +11,7 @@ import java.util.List; @Configuration -@Profile("!prod && !test") +@Profile("!production && !test") public class OpenApiConfig { @Value("${springdoc.swagger-ui.server.domain:http://localhost:8080}") diff --git a/src/main/java/au/org/aodn/oceancurrent/configuration/SecurityConfig.java b/src/main/java/au/org/aodn/oceancurrent/configuration/SecurityConfig.java index b16489d..9085b82 100644 --- a/src/main/java/au/org/aodn/oceancurrent/configuration/SecurityConfig.java +++ b/src/main/java/au/org/aodn/oceancurrent/configuration/SecurityConfig.java @@ -34,7 +34,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .csrf(AbstractHttpConfigurer::disable) .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - // Add EC2 instance authentication filter if available (only in prod/edge profiles) + // Add EC2 instance authentication filter if available (only in production/edge profiles) ec2InstanceAuthenticationFilter.ifPresent(filter -> httpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class) ); diff --git a/src/main/java/au/org/aodn/oceancurrent/controller/MonitoringController.java b/src/main/java/au/org/aodn/oceancurrent/controller/MonitoringController.java index 5daf64e..9cfc0fa 100644 --- a/src/main/java/au/org/aodn/oceancurrent/controller/MonitoringController.java +++ b/src/main/java/au/org/aodn/oceancurrent/controller/MonitoringController.java @@ -36,7 +36,7 @@ public class MonitoringController { ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Fatal log generated successfully"), - @ApiResponse(responseCode = "401", description = "Unauthorised - EC2 instance authentication required (prod/edge only)") + @ApiResponse(responseCode = "401", description = "Unauthorised - EC2 instance authentication required (production/edge only)") }) public ResponseEntity triggerFatalLog( @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Optional monitoring request with custom error message")