Fixed and added more health support plus network endpoint

This commit is contained in:
litoral05
2026-05-08 16:44:34 +01:00
parent 2545f285eb
commit 8363926ec8
6 changed files with 199 additions and 12 deletions
@@ -31,10 +31,15 @@ public class ApiKeyAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
filterChain.doFilter(request, response);
return;
}
String providedApiKey = request.getHeader(API_KEY_HEADER);
String expectedApiKey = securityProperties.getApiKey();
@@ -1,14 +1,26 @@
package com.litoralregas.vpnorchestrator.config;
import com.litoralregas.vpnorchestrator.auth.ApiKeyAuthFilter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableConfigurationProperties({
AppSecurityProperties.class,
@@ -18,28 +30,95 @@ public class SecurityConfig {
private final AppSecurityProperties securityProperties;
public SecurityConfig(AppSecurityProperties securityProperties) {
public SecurityConfig(
AppSecurityProperties securityProperties
) {
this.securityProperties = securityProperties;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
ApiKeyAuthFilter apiKeyAuthFilter = new ApiKeyAuthFilter(securityProperties);
public SecurityFilterChain securityFilterChain(
HttpSecurity http
) throws Exception {
ApiKeyAuthFilter apiKeyAuthFilter =
new ApiKeyAuthFilter(securityProperties);
return http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.cors(cors -> {
})
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable())
.logout(logout -> logout.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionManagement(session ->
session.sessionCreationPolicy(
SessionCreationPolicy.STATELESS
)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.requestMatchers(
"/actuator/health"
).permitAll()
.requestMatchers(
HttpMethod.OPTIONS,
"/**"
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(
apiKeyAuthFilter,
UsernamePasswordAuthenticationFilter.class
)
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration =
new CorsConfiguration();
configuration.setAllowedOrigins(
List.of(
"http://localhost:1420"
)
);
configuration.setAllowedMethods(
List.of(
"GET",
"POST",
"PUT",
"DELETE",
"OPTIONS"
)
);
configuration.setAllowedHeaders(
List.of("*")
);
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(
"/**",
configuration
);
return source;
}
}
@@ -1,5 +1,6 @@
package com.litoralregas.vpnorchestrator.vps;
import com.litoralregas.vpnorchestrator.vps.dto.NetworkTrafficResponse;
import com.litoralregas.vpnorchestrator.vps.dto.VpsHealthResponse;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@@ -26,4 +27,9 @@ public class VpsController {
public String rollbackLastBackup() {
return wireGuardService.restoreLastWireGuardBackup();
}
@GetMapping("/network-traffic")
public NetworkTrafficResponse getNetworkTraffic() {
return wireGuardService.getNetworkTraffic();
}
}
@@ -2,6 +2,7 @@ package com.litoralregas.vpnorchestrator.vps;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.litoralregas.vpnorchestrator.vps.dto.NetworkTrafficResponse;
import com.litoralregas.vpnorchestrator.vps.dto.VpsHealthResponse;
import org.springframework.stereotype.Service;
@@ -76,19 +77,57 @@ public class WireGuardService {
}
public VpsHealthResponse getVpsHealth() {
SshCommandResult result = sshService.executeOnConfiguredVps(
"sudo /usr/local/sbin/lr-vps-health"
);
if (result.exitCode() != 0) {
throw new SshCommandException(
"Failed to query VPS health: " + result.stderr()
"Failed to query VPS health: "
+ result.stderr()
);
}
try {
return objectMapper.readValue(result.stdout(), VpsHealthResponse.class);
VpsHealthResponse base =
objectMapper.readValue(
result.stdout(),
VpsHealthResponse.class
);
Set<String> usedIps =
findUsedVpnIps();
return new VpsHealthResponse(
base.wireGuardInterface(),
base.wireGuardRunning(),
base.wireGuardPeerCount(),
base.wireGuardConfigExists(),
base.udp2rawService(),
base.udp2rawActive(),
base.latestWireGuardBackup(),
base.systemUptime(),
base.diskUsagePercent(),
base.memoryUsagePercent(),
base.loadAverage(),
base.publicIp(),
true,
usedIps.size(),
65534,
java.time.Instant.now().toString()
);
} catch (JsonProcessingException e) {
throw new IllegalStateException(
"Invalid VPS health JSON returned by script",
e
@@ -123,4 +162,34 @@ public class WireGuardService {
return result.stdout();
}
public NetworkTrafficResponse getNetworkTraffic() {
SshCommandResult result =
sshService.executeOnConfiguredVps(
"sudo /usr/local/sbin/lr-vps-network-traffic"
);
if (result.exitCode() != 0) {
throw new SshCommandException(
"Failed to query network traffic: "
+ result.stderr()
);
}
try {
return objectMapper.readValue(
result.stdout(),
NetworkTrafficResponse.class
);
} catch (JsonProcessingException e) {
throw new IllegalStateException(
"Invalid network traffic JSON returned by script",
e
);
}
}
}
@@ -0,0 +1,17 @@
package com.litoralregas.vpnorchestrator.vps.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
public record NetworkTrafficResponse(
@JsonProperty("interface")
String interfaceName,
int sampleSeconds,
long rxBytesPerSecond,
long txBytesPerSecond,
double downloadMbps,
double uploadMbps,
String updatedAt
) {
}
@@ -1,17 +1,28 @@
package com.litoralregas.vpnorchestrator.vps.dto;
public record VpsHealthResponse(
String wireGuardInterface,
boolean wireGuardRunning,
int wireGuardPeerCount,
boolean wireGuardConfigExists,
String udp2rawService,
boolean udp2rawActive,
String latestWireGuardBackup,
String systemUptime,
int diskUsagePercent,
int memoryUsagePercent,
String loadAverage,
String publicIp
String publicIp,
boolean backend,
int ipPoolUsed,
int ipPoolTotal,
String updatedAt
) {
}