Validate IP allocation against live OpenVPN clients
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package com.litoralregas.openvpn.openvpn;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public class CreateIpAllocationRequest {
|
||||
|
||||
@NotBlank
|
||||
private String clientName;
|
||||
|
||||
@NotNull
|
||||
private IpAllocationMode allocationMode;
|
||||
|
||||
private Integer number;
|
||||
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
public IpAllocationMode getAllocationMode() {
|
||||
return allocationMode;
|
||||
}
|
||||
|
||||
public Integer getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public void setAllocationMode(IpAllocationMode allocationMode) {
|
||||
this.allocationMode = allocationMode;
|
||||
}
|
||||
|
||||
public void setNumber(Integer number) {
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.litoralregas.openvpn.openvpn;
|
||||
|
||||
import com.litoralregas.openvpn.router.Router;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ip_allocations")
|
||||
public class IpAllocation {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "router_id", nullable = false)
|
||||
private Router router;
|
||||
|
||||
private String clientName;
|
||||
private String lanSubnet;
|
||||
private String vpnIp;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private IpAllocationMode allocationMode;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public IpAllocation() {
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Router getRouter() {
|
||||
return router;
|
||||
}
|
||||
|
||||
public void setRouter(Router router) {
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public String getLanSubnet() {
|
||||
return lanSubnet;
|
||||
}
|
||||
|
||||
public void setLanSubnet(String lanSubnet) {
|
||||
this.lanSubnet = lanSubnet;
|
||||
}
|
||||
|
||||
public String getVpnIp() {
|
||||
return vpnIp;
|
||||
}
|
||||
|
||||
public void setVpnIp(String vpnIp) {
|
||||
this.vpnIp = vpnIp;
|
||||
}
|
||||
|
||||
public IpAllocationMode getAllocationMode() {
|
||||
return allocationMode;
|
||||
}
|
||||
|
||||
public void setAllocationMode(IpAllocationMode allocationMode) {
|
||||
this.allocationMode = allocationMode;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
// getters and setters
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.litoralregas.openvpn.openvpn;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/routers/{routerId}/ip-allocation")
|
||||
public class IpAllocationController {
|
||||
|
||||
private final IpAllocationService service;
|
||||
|
||||
public IpAllocationController(IpAllocationService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public IpAllocationResponse allocate(
|
||||
@PathVariable UUID routerId,
|
||||
@Valid @RequestBody CreateIpAllocationRequest request
|
||||
) {
|
||||
return service.allocate(routerId, request);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{allocationId}")
|
||||
public void delete(@PathVariable UUID allocationId) {
|
||||
service.delete(allocationId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.litoralregas.openvpn.openvpn;
|
||||
|
||||
public enum IpAllocationMode {
|
||||
AUTOMATIC,
|
||||
MANUAL
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.litoralregas.openvpn.openvpn;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface IpAllocationRepository extends JpaRepository<IpAllocation, UUID> {
|
||||
|
||||
boolean existsByClientName(String clientName);
|
||||
|
||||
boolean existsByLanSubnet(String lanSubnet);
|
||||
|
||||
boolean existsByVpnIp(String vpnIp);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.litoralregas.openvpn.openvpn;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record IpAllocationResponse(
|
||||
UUID id,
|
||||
UUID routerId,
|
||||
String routerName,
|
||||
String clientName,
|
||||
String lanSubnet,
|
||||
String vpnIp,
|
||||
IpAllocationMode allocationMode,
|
||||
LocalDateTime createdAt
|
||||
) {
|
||||
public static IpAllocationResponse from(IpAllocation allocation) {
|
||||
return new IpAllocationResponse(
|
||||
allocation.getId(),
|
||||
allocation.getRouter().getId(),
|
||||
allocation.getRouter().getName(),
|
||||
allocation.getClientName(),
|
||||
allocation.getLanSubnet(),
|
||||
allocation.getVpnIp(),
|
||||
allocation.getAllocationMode(),
|
||||
allocation.getCreatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.litoralregas.openvpn.openvpn;
|
||||
|
||||
import com.litoralregas.openvpn.router.Router;
|
||||
import com.litoralregas.openvpn.router.RouterService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class IpAllocationService {
|
||||
|
||||
private final IpAllocationRepository repository;
|
||||
private final RouterService routerService;
|
||||
private final OpenVpnService openVpnService;
|
||||
|
||||
public IpAllocationService(
|
||||
IpAllocationRepository repository,
|
||||
RouterService routerService,
|
||||
OpenVpnService openVpnService) {
|
||||
this.repository = repository;
|
||||
this.routerService = routerService;
|
||||
this.openVpnService = openVpnService;
|
||||
}
|
||||
|
||||
public IpAllocationResponse allocate(UUID routerId, CreateIpAllocationRequest request) {
|
||||
Router router = routerService.findById(routerId);
|
||||
|
||||
if (repository.existsByClientName(request.getClientName())) {
|
||||
throw new IllegalArgumentException("Client name already allocated: " + request.getClientName());
|
||||
}
|
||||
|
||||
int number = resolveNumber(request);
|
||||
|
||||
String lanSubnet = "192.168." + number + ".0";
|
||||
String vpnIp = "198.20.1." + number;
|
||||
|
||||
validateNumber(number);
|
||||
validateNoDuplicates(lanSubnet, vpnIp);
|
||||
validateAgainstLiveClients(
|
||||
request.getClientName(),
|
||||
vpnIp,
|
||||
lanSubnet
|
||||
);
|
||||
|
||||
IpAllocation allocation = new IpAllocation();
|
||||
allocation.setId(UUID.randomUUID());
|
||||
allocation.setRouter(router);
|
||||
allocation.setClientName(request.getClientName());
|
||||
allocation.setLanSubnet(lanSubnet);
|
||||
allocation.setVpnIp(vpnIp);
|
||||
allocation.setAllocationMode(request.getAllocationMode());
|
||||
allocation.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
return IpAllocationResponse.from(repository.save(allocation));
|
||||
}
|
||||
|
||||
private int resolveNumber(CreateIpAllocationRequest request) {
|
||||
if (request.getAllocationMode() == IpAllocationMode.MANUAL) {
|
||||
if (request.getNumber() == null) {
|
||||
throw new IllegalArgumentException("Manual allocation requires a number");
|
||||
}
|
||||
|
||||
return request.getNumber();
|
||||
}
|
||||
|
||||
for (int number = 2; number <= 254; number++) {
|
||||
String lanSubnet = "192.168." + number + ".0";
|
||||
String vpnIp = "198.20.1." + number;
|
||||
|
||||
if (!repository.existsByLanSubnet(lanSubnet) && !repository.existsByVpnIp(vpnIp)) {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("No available IP allocations");
|
||||
}
|
||||
|
||||
private void validateNumber(int number) {
|
||||
if (number < 2 || number > 254) {
|
||||
throw new IllegalArgumentException("Allocation number must be between 2 and 254");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNoDuplicates(String lanSubnet, String vpnIp) {
|
||||
if (repository.existsByLanSubnet(lanSubnet)) {
|
||||
throw new IllegalArgumentException("LAN subnet already allocated: " + lanSubnet);
|
||||
}
|
||||
|
||||
if (repository.existsByVpnIp(vpnIp)) {
|
||||
throw new IllegalArgumentException("VPN IP already allocated: " + vpnIp);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAgainstLiveClients(String clientName, String vpnIp, String lanSubnet) {
|
||||
var clients = openVpnService.listClients();
|
||||
|
||||
for (var client : clients) {
|
||||
if (client.clientName().equalsIgnoreCase(clientName)) {
|
||||
throw new IllegalArgumentException("Client already exists on VPS: " + clientName);
|
||||
}
|
||||
|
||||
if (client.vpnIp().equals(vpnIp)) {
|
||||
throw new IllegalArgumentException("VPN IP already in use on VPS: " + vpnIp);
|
||||
}
|
||||
|
||||
if (client.lanSubnet().equals(lanSubnet)) {
|
||||
throw new IllegalArgumentException("LAN subnet already in use on VPS: " + lanSubnet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(UUID id) {
|
||||
repository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.litoralregas.openvpn.ssh;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/ssh-test")
|
||||
public class SshTestController {
|
||||
|
||||
private final SshService sshService;
|
||||
|
||||
public SshTestController(SshService sshService) {
|
||||
this.sshService = sshService;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public SshCommandResult test() {
|
||||
return sshService.executeOnConfiguredVps(
|
||||
"/var/litoral_regas_openvpn/tools/list-clients.sh"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.litoralregas.openvpn.ssh;
|
||||
|
||||
public record SshTestRequest(
|
||||
String host,
|
||||
int port,
|
||||
String username,
|
||||
String password
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE ip_allocations (
|
||||
id UUID PRIMARY KEY,
|
||||
|
||||
router_id UUID NOT NULL,
|
||||
|
||||
client_name VARCHAR(120) NOT NULL,
|
||||
lan_subnet VARCHAR(50) NOT NULL,
|
||||
vpn_ip VARCHAR(50) NOT NULL,
|
||||
|
||||
allocation_mode VARCHAR(40) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||
|
||||
CONSTRAINT fk_ip_allocations_router
|
||||
FOREIGN KEY (router_id)
|
||||
REFERENCES routers (id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT uk_ip_allocations_lan_subnet UNIQUE (lan_subnet),
|
||||
CONSTRAINT uk_ip_allocations_vpn_ip UNIQUE (vpn_ip),
|
||||
CONSTRAINT uk_ip_allocations_client_name UNIQUE (client_name)
|
||||
);
|
||||
Reference in New Issue
Block a user