From 8363926ec8dfdf5d7511698c11f64b1c0fb2014f Mon Sep 17 00:00:00 2001 From: litoral05 Date: Fri, 8 May 2026 16:44:34 +0100 Subject: [PATCH] Fixed and added more health support plus network endpoint --- .../auth/ApiKeyAuthFilter.java | 5 + .../config/SecurityConfig.java | 97 +++++++++++++++++-- .../vpnorchestrator/vps/VpsController.java | 6 ++ .../vpnorchestrator/vps/WireGuardService.java | 73 +++++++++++++- .../vps/dto/NetworkTrafficResponse.java | 17 ++++ .../vps/dto/VpsHealthResponse.java | 13 ++- 6 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/litoralregas/vpnorchestrator/vps/dto/NetworkTrafficResponse.java diff --git a/src/main/java/com/litoralregas/vpnorchestrator/auth/ApiKeyAuthFilter.java b/src/main/java/com/litoralregas/vpnorchestrator/auth/ApiKeyAuthFilter.java index be9f495..9d5a3e0 100644 --- a/src/main/java/com/litoralregas/vpnorchestrator/auth/ApiKeyAuthFilter.java +++ b/src/main/java/com/litoralregas/vpnorchestrator/auth/ApiKeyAuthFilter.java @@ -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(); diff --git a/src/main/java/com/litoralregas/vpnorchestrator/config/SecurityConfig.java b/src/main/java/com/litoralregas/vpnorchestrator/config/SecurityConfig.java index 9157e99..a14f54a 100644 --- a/src/main/java/com/litoralregas/vpnorchestrator/config/SecurityConfig.java +++ b/src/main/java/com/litoralregas/vpnorchestrator/config/SecurityConfig.java @@ -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; + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnorchestrator/vps/VpsController.java b/src/main/java/com/litoralregas/vpnorchestrator/vps/VpsController.java index a7d61e3..0ef90ef 100644 --- a/src/main/java/com/litoralregas/vpnorchestrator/vps/VpsController.java +++ b/src/main/java/com/litoralregas/vpnorchestrator/vps/VpsController.java @@ -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(); + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnorchestrator/vps/WireGuardService.java b/src/main/java/com/litoralregas/vpnorchestrator/vps/WireGuardService.java index 07675bf..bcc0537 100644 --- a/src/main/java/com/litoralregas/vpnorchestrator/vps/WireGuardService.java +++ b/src/main/java/com/litoralregas/vpnorchestrator/vps/WireGuardService.java @@ -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 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 + ); + } + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnorchestrator/vps/dto/NetworkTrafficResponse.java b/src/main/java/com/litoralregas/vpnorchestrator/vps/dto/NetworkTrafficResponse.java new file mode 100644 index 0000000..35dad4c --- /dev/null +++ b/src/main/java/com/litoralregas/vpnorchestrator/vps/dto/NetworkTrafficResponse.java @@ -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 +) { +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnorchestrator/vps/dto/VpsHealthResponse.java b/src/main/java/com/litoralregas/vpnorchestrator/vps/dto/VpsHealthResponse.java index be96e7b..6219f43 100644 --- a/src/main/java/com/litoralregas/vpnorchestrator/vps/dto/VpsHealthResponse.java +++ b/src/main/java/com/litoralregas/vpnorchestrator/vps/dto/VpsHealthResponse.java @@ -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 ) { } \ No newline at end of file