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