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 @Override
protected void doFilterInternal( protected void doFilterInternal(
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
FilterChain filterChain FilterChain filterChain
) throws ServletException, IOException { ) throws ServletException, IOException {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
filterChain.doFilter(request, response);
return;
}
String providedApiKey = request.getHeader(API_KEY_HEADER); String providedApiKey = request.getHeader(API_KEY_HEADER);
String expectedApiKey = securityProperties.getApiKey(); String expectedApiKey = securityProperties.getApiKey();
@@ -1,14 +1,26 @@
package com.litoralregas.vpnorchestrator.config; package com.litoralregas.vpnorchestrator.config;
import com.litoralregas.vpnorchestrator.auth.ApiKeyAuthFilter; import com.litoralregas.vpnorchestrator.auth.ApiKeyAuthFilter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 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 @Configuration
@EnableConfigurationProperties({ @EnableConfigurationProperties({
AppSecurityProperties.class, AppSecurityProperties.class,
@@ -18,28 +30,95 @@ public class SecurityConfig {
private final AppSecurityProperties securityProperties; private final AppSecurityProperties securityProperties;
public SecurityConfig(AppSecurityProperties securityProperties) { public SecurityConfig(
AppSecurityProperties securityProperties
) {
this.securityProperties = securityProperties; this.securityProperties = securityProperties;
} }
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain securityFilterChain(
ApiKeyAuthFilter apiKeyAuthFilter = new ApiKeyAuthFilter(securityProperties); HttpSecurity http
) throws Exception {
ApiKeyAuthFilter apiKeyAuthFilter =
new ApiKeyAuthFilter(securityProperties);
return http return http
.csrf(csrf -> csrf.disable()) .csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.cors(cors -> {
})
.formLogin(form -> form.disable()) .formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable()) .httpBasic(basic -> basic.disable())
.logout(logout -> logout.disable()) .logout(logout -> logout.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) .sessionManagement(session ->
session.sessionCreationPolicy(
SessionCreationPolicy.STATELESS
)
) )
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll() .requestMatchers(
"/actuator/health"
).permitAll()
.requestMatchers(
HttpMethod.OPTIONS,
"/**"
).permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(
apiKeyAuthFilter,
UsernamePasswordAuthenticationFilter.class
)
.build(); .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; package com.litoralregas.vpnorchestrator.vps;
import com.litoralregas.vpnorchestrator.vps.dto.NetworkTrafficResponse;
import com.litoralregas.vpnorchestrator.vps.dto.VpsHealthResponse; import com.litoralregas.vpnorchestrator.vps.dto.VpsHealthResponse;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -26,4 +27,9 @@ public class VpsController {
public String rollbackLastBackup() { public String rollbackLastBackup() {
return wireGuardService.restoreLastWireGuardBackup(); 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.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.litoralregas.vpnorchestrator.vps.dto.NetworkTrafficResponse;
import com.litoralregas.vpnorchestrator.vps.dto.VpsHealthResponse; import com.litoralregas.vpnorchestrator.vps.dto.VpsHealthResponse;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -76,19 +77,57 @@ public class WireGuardService {
} }
public VpsHealthResponse getVpsHealth() { public VpsHealthResponse getVpsHealth() {
SshCommandResult result = sshService.executeOnConfiguredVps( SshCommandResult result = sshService.executeOnConfiguredVps(
"sudo /usr/local/sbin/lr-vps-health" "sudo /usr/local/sbin/lr-vps-health"
); );
if (result.exitCode() != 0) { if (result.exitCode() != 0) {
throw new SshCommandException( throw new SshCommandException(
"Failed to query VPS health: " + result.stderr() "Failed to query VPS health: "
+ result.stderr()
); );
} }
try { 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) { } catch (JsonProcessingException e) {
throw new IllegalStateException( throw new IllegalStateException(
"Invalid VPS health JSON returned by script", "Invalid VPS health JSON returned by script",
e e
@@ -123,4 +162,34 @@ public class WireGuardService {
return result.stdout(); 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; package com.litoralregas.vpnorchestrator.vps.dto;
public record VpsHealthResponse( public record VpsHealthResponse(
String wireGuardInterface, String wireGuardInterface,
boolean wireGuardRunning, boolean wireGuardRunning,
int wireGuardPeerCount, int wireGuardPeerCount,
boolean wireGuardConfigExists, boolean wireGuardConfigExists,
String udp2rawService, String udp2rawService,
boolean udp2rawActive, boolean udp2rawActive,
String latestWireGuardBackup, String latestWireGuardBackup,
String systemUptime, String systemUptime,
int diskUsagePercent, int diskUsagePercent,
int memoryUsagePercent, int memoryUsagePercent,
String loadAverage, String loadAverage,
String publicIp String publicIp,
boolean backend,
int ipPoolUsed,
int ipPoolTotal,
String updatedAt
) { ) {
} }