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 Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
16 changes: 8 additions & 8 deletions docs/EC2_AUTHENTICATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions scripts/test_local_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
"""
Expand Down Expand Up @@ -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...")
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import java.util.List;

@Configuration
@Profile("!prod && !test")
@Profile("!production && !test")
public class OpenApiConfig {
Comment on lines 13 to 15

@Value("${springdoc.swagger-ui.server.domain:http://localhost:8080}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MonitoringResponse> triggerFatalLog(
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Optional monitoring request with custom error message")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <p>
* Validates EC2 instance identity using:
* 1. Instance identity document from EC2 metadata service
Expand All @@ -38,7 +39,7 @@
* - Whitelist prevents unauthorised instances from accessing the endpoint
*/
@Component
@Profile({"prod", "edge"})
@Profile({"production", "edge"})
@Slf4j
Comment thread
weited marked this conversation as resolved.
@RequiredArgsConstructor
public class Ec2InstanceAuthenticationFilter extends OncePerRequestFilter {
Expand All @@ -53,7 +54,8 @@ public class Ec2InstanceAuthenticationFilter extends OncePerRequestFilter {

@PostConstruct
public void init() {
this.authorisedInstanceIds = new HashSet<>(monitoringSecurityProperties.getAuthorisedInstanceIds());
List<String> configuredIds = monitoringSecurityProperties.getAuthorisedInstanceIds();
this.authorisedInstanceIds = configuredIds != null ? new HashSet<>(configuredIds) : Collections.emptySet();
log.info("Initialized EC2 authentication filter with {} authorised instance IDs", authorisedInstanceIds.size());
}
Comment thread
weited marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
@@ -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"));
}
}