Add WireGuard VPS sync bootstrap flow
This commit is contained in:
-30
@@ -1,30 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.ipam.controller;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.ipam.dto.VpnIpAllocationResponse;
|
||||
import com.litoralregas.vpnprovisioner.ipam.service.VpnIpAllocationService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/ipam")
|
||||
public class VpnIpAllocationController {
|
||||
|
||||
private final VpnIpAllocationService service;
|
||||
|
||||
public VpnIpAllocationController(VpnIpAllocationService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping("/routers/{routerId}/allocate")
|
||||
public VpnIpAllocationResponse allocate(@PathVariable UUID routerId) {
|
||||
return service.allocateForRouter(routerId);
|
||||
}
|
||||
|
||||
@PostMapping("/routers/{routerId}/release")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void release(@PathVariable UUID routerId) {
|
||||
service.releaseForRouter(routerId);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.ipam.dto;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.ipam.entity.VpnIpAllocationStatus;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
public record VpnIpAllocationResponse(
|
||||
UUID id,
|
||||
String ipAddress,
|
||||
VpnIpAllocationStatus status,
|
||||
UUID routerId,
|
||||
Instant allocatedAt,
|
||||
Instant releasedAt
|
||||
) {
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.ipam.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "vpn_ip_allocations")
|
||||
public class VpnIpAllocation {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private String ipAddress;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private VpnIpAllocationStatus status;
|
||||
|
||||
private UUID routerId;
|
||||
|
||||
private Instant allocatedAt;
|
||||
|
||||
private Instant releasedAt;
|
||||
|
||||
protected VpnIpAllocation() {
|
||||
}
|
||||
|
||||
public VpnIpAllocation(String ipAddress) {
|
||||
this.id = UUID.randomUUID();
|
||||
this.ipAddress = ipAddress;
|
||||
this.status = VpnIpAllocationStatus.ALLOCATED_EXTERNAL;
|
||||
this.allocatedAt = Instant.now();
|
||||
}
|
||||
|
||||
public static VpnIpAllocation backendAllocated(String ipAddress, UUID routerId) {
|
||||
VpnIpAllocation allocation = new VpnIpAllocation(ipAddress);
|
||||
allocation.allocateTo(routerId);
|
||||
return allocation;
|
||||
}
|
||||
|
||||
public void allocateTo(UUID routerId) {
|
||||
this.status = VpnIpAllocationStatus.ALLOCATED;
|
||||
this.routerId = routerId;
|
||||
this.allocatedAt = Instant.now();
|
||||
this.releasedAt = null;
|
||||
}
|
||||
|
||||
public void markExternal() {
|
||||
if (this.status != VpnIpAllocationStatus.ALLOCATED) {
|
||||
this.status = VpnIpAllocationStatus.ALLOCATED_EXTERNAL;
|
||||
this.allocatedAt = this.allocatedAt == null ? Instant.now() : this.allocatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
this.status = VpnIpAllocationStatus.RELEASED;
|
||||
this.routerId = null;
|
||||
this.releasedAt = Instant.now();
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public VpnIpAllocationStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public UUID getRouterId() {
|
||||
return routerId;
|
||||
}
|
||||
|
||||
public Instant getAllocatedAt() {
|
||||
return allocatedAt;
|
||||
}
|
||||
|
||||
public Instant getReleasedAt() {
|
||||
return releasedAt;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.ipam.entity;
|
||||
|
||||
public enum VpnIpAllocationStatus {
|
||||
ALLOCATED,
|
||||
ALLOCATED_EXTERNAL,
|
||||
RELEASED,
|
||||
RETIRED
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.ipam.repository;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.ipam.entity.VpnIpAllocation;
|
||||
import org.springframework.data.jpa.repository.*;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface VpnIpAllocationRepository extends JpaRepository<VpnIpAllocation, UUID> {
|
||||
|
||||
Optional<VpnIpAllocation> findByRouterId(UUID routerId);
|
||||
|
||||
Optional<VpnIpAllocation> findByIpAddress(String ipAddress);
|
||||
|
||||
@Modifying
|
||||
@Query("""
|
||||
UPDATE VpnIpAllocation ip
|
||||
SET ip.status = 'RELEASED',
|
||||
ip.routerId = null,
|
||||
ip.releasedAt = CURRENT_TIMESTAMP
|
||||
WHERE ip.routerId = :routerId
|
||||
""")
|
||||
void releaseByRouterId(@Param("routerId") UUID routerId);
|
||||
}
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.ipam.service;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.common.exception.ResourceNotFoundException;
|
||||
import com.litoralregas.vpnprovisioner.ipam.dto.VpnIpAllocationResponse;
|
||||
import com.litoralregas.vpnprovisioner.ipam.entity.VpnIpAllocation;
|
||||
import com.litoralregas.vpnprovisioner.ipam.repository.VpnIpAllocationRepository;
|
||||
import com.litoralregas.vpnprovisioner.router.entity.Router;
|
||||
import com.litoralregas.vpnprovisioner.router.repository.RouterRepository;
|
||||
import com.litoralregas.vpnprovisioner.vps.WireGuardVpsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class VpnIpAllocationService {
|
||||
|
||||
private static final int START_THIRD_OCTET = 1;
|
||||
private static final int END_THIRD_OCTET = 255;
|
||||
private static final int START_FOURTH_OCTET = 1;
|
||||
private static final int END_FOURTH_OCTET = 254;
|
||||
|
||||
private final VpnIpAllocationRepository vpnIpAllocationRepository;
|
||||
private final RouterRepository routerRepository;
|
||||
private final WireGuardVpsService wireGuardVpsService;
|
||||
|
||||
public VpnIpAllocationService(
|
||||
VpnIpAllocationRepository vpnIpAllocationRepository,
|
||||
RouterRepository routerRepository,
|
||||
WireGuardVpsService wireGuardVpsService
|
||||
) {
|
||||
this.vpnIpAllocationRepository = vpnIpAllocationRepository;
|
||||
this.routerRepository = routerRepository;
|
||||
this.wireGuardVpsService = wireGuardVpsService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public VpnIpAllocationResponse allocateForRouter(UUID routerId) {
|
||||
Router router = routerRepository.findById(routerId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Router not found: " + routerId));
|
||||
|
||||
return vpnIpAllocationRepository.findByRouterId(routerId)
|
||||
.map(this::toResponse)
|
||||
.orElseGet(() -> allocateNewIp(router));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void releaseForRouter(UUID routerId) {
|
||||
vpnIpAllocationRepository.releaseByRouterId(routerId);
|
||||
}
|
||||
|
||||
private VpnIpAllocationResponse allocateNewIp(Router router) {
|
||||
Set<String> usedOnVps = refreshUsedIpsFromVps();
|
||||
|
||||
Set<String> knownInDb = vpnIpAllocationRepository.findAll()
|
||||
.stream()
|
||||
.map(VpnIpAllocation::getIpAddress)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
String nextIp = findNextAvailableIp(usedOnVps, knownInDb);
|
||||
|
||||
VpnIpAllocation allocation = VpnIpAllocation.backendAllocated(nextIp, router.getId());
|
||||
|
||||
VpnIpAllocation saved = vpnIpAllocationRepository.save(allocation);
|
||||
|
||||
router.assignVpnIp(saved.getIpAddress());
|
||||
|
||||
return toResponse(saved);
|
||||
}
|
||||
|
||||
private Set<String> refreshUsedIpsFromVps() {
|
||||
Set<String> usedIps = wireGuardVpsService.findUsedVpnIps();
|
||||
|
||||
for (String ip : usedIps) {
|
||||
vpnIpAllocationRepository.findByIpAddress(ip)
|
||||
.ifPresentOrElse(
|
||||
VpnIpAllocation::markExternal,
|
||||
() -> vpnIpAllocationRepository.save(new VpnIpAllocation(ip))
|
||||
);
|
||||
}
|
||||
|
||||
return usedIps;
|
||||
}
|
||||
|
||||
private String findNextAvailableIp(Set<String> usedOnVps, Set<String> knownInDb) {
|
||||
for (int thirdOctet = START_THIRD_OCTET; thirdOctet <= END_THIRD_OCTET; thirdOctet++) {
|
||||
for (int fourthOctet = START_FOURTH_OCTET; fourthOctet <= END_FOURTH_OCTET; fourthOctet++) {
|
||||
String ip = "198.19." + thirdOctet + "." + fourthOctet;
|
||||
|
||||
if (!usedOnVps.contains(ip) && !knownInDb.contains(ip)) {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("No free VPN IP addresses available");
|
||||
}
|
||||
|
||||
private VpnIpAllocationResponse toResponse(VpnIpAllocation allocation) {
|
||||
return new VpnIpAllocationResponse(
|
||||
allocation.getId(),
|
||||
allocation.getIpAddress(),
|
||||
allocation.getStatus(),
|
||||
allocation.getRouterId(),
|
||||
allocation.getAllocatedAt(),
|
||||
allocation.getReleasedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -1,10 +1,8 @@
|
||||
package com.litoralregas.vpnprovisioner.router.controller;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.router.dto.CreateRouterRequest;
|
||||
import com.litoralregas.vpnprovisioner.router.dto.ProvisionRouterVpnRequest;
|
||||
import com.litoralregas.vpnprovisioner.router.dto.RouterResponse;
|
||||
import com.litoralregas.vpnprovisioner.router.dto.UpdateRouterRequest;
|
||||
import com.litoralregas.vpnprovisioner.router.dto.*;
|
||||
import com.litoralregas.vpnprovisioner.router.service.RouterService;
|
||||
import com.litoralregas.vpnprovisioner.router.service.RouterSyncService;
|
||||
import com.litoralregas.vpnprovisioner.router.service.RouterVpnProvisioningService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -19,10 +17,12 @@ public class RouterController {
|
||||
|
||||
private final RouterService routerService;
|
||||
private final RouterVpnProvisioningService routerVpnProvisioningService;
|
||||
private final RouterSyncService routerSyncService;
|
||||
|
||||
public RouterController(RouterService routerService, RouterVpnProvisioningService routerVpnProvisioningService) {
|
||||
public RouterController(RouterService routerService, RouterVpnProvisioningService routerVpnProvisioningService, RouterSyncService routerSyncService) {
|
||||
this.routerService = routerService;
|
||||
this.routerVpnProvisioningService = routerVpnProvisioningService;
|
||||
this.routerSyncService = routerSyncService;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@@ -62,4 +62,9 @@ public class RouterController {
|
||||
) {
|
||||
return routerVpnProvisioningService.provision(id, request);
|
||||
}
|
||||
|
||||
@PostMapping("/sync-from-vps")
|
||||
public SyncRoutersFromVpsResponse syncFromVps() {
|
||||
return routerSyncService.syncFromVps();
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.litoralregas.vpnprovisioner.router.dto;
|
||||
|
||||
public record SyncRoutersFromVpsResponse(
|
||||
int seenOnVps,
|
||||
int created,
|
||||
int updated
|
||||
) {
|
||||
}
|
||||
@@ -138,4 +138,31 @@ public class Router {
|
||||
public Instant getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public static Router importedVpnRouter(String publicKey, String vpnIp) {
|
||||
Router router = new Router(
|
||||
"Imported Router " + vpnIp,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
router.wireguardPublicKey = publicKey;
|
||||
router.vpnIp = vpnIp;
|
||||
router.endpointMode = EndpointMode.UDP2RAW;
|
||||
router.vpnStatus = RouterVpnStatus.APPLIED;
|
||||
router.vpnProvisionedAt = Instant.now();
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
public void syncVpnPeer(String publicKey, String vpnIp) {
|
||||
this.wireguardPublicKey = publicKey;
|
||||
this.vpnIp = vpnIp;
|
||||
this.endpointMode = EndpointMode.UDP2RAW;
|
||||
this.vpnStatus = RouterVpnStatus.APPLIED;
|
||||
|
||||
if (this.vpnProvisionedAt == null) {
|
||||
this.vpnProvisionedAt = Instant.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package com.litoralregas.vpnprovisioner.router.repository;
|
||||
import com.litoralregas.vpnprovisioner.router.entity.Router;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface RouterRepository extends JpaRepository<Router, UUID> {
|
||||
Optional<Router> findByWireguardPublicKey(String wireguardPublicKey);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.litoralregas.vpnprovisioner.router.service;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.router.dto.SyncRoutersFromVpsResponse;
|
||||
import com.litoralregas.vpnprovisioner.router.entity.Router;
|
||||
import com.litoralregas.vpnprovisioner.router.repository.RouterRepository;
|
||||
import com.litoralregas.vpnprovisioner.vps.WireGuardVpsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
public class RouterSyncService {
|
||||
|
||||
private static final Pattern LINE_PATTERN =
|
||||
Pattern.compile("^(.+?)\\s+([0-9.]+/32)$");
|
||||
|
||||
private final WireGuardVpsService wireGuardVpsService;
|
||||
private final RouterRepository routerRepository;
|
||||
|
||||
public RouterSyncService(
|
||||
WireGuardVpsService wireGuardVpsService,
|
||||
RouterRepository routerRepository
|
||||
) {
|
||||
this.wireGuardVpsService = wireGuardVpsService;
|
||||
this.routerRepository = routerRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SyncRoutersFromVpsResponse syncFromVps() {
|
||||
|
||||
String output = wireGuardVpsService.showAllowedIps();
|
||||
|
||||
List<String> lines = output.lines()
|
||||
.filter(line -> !line.isBlank())
|
||||
.toList();
|
||||
|
||||
int created = 0;
|
||||
int updated = 0;
|
||||
|
||||
for (String line : lines) {
|
||||
|
||||
Matcher matcher = LINE_PATTERN.matcher(line.trim());
|
||||
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String publicKey = matcher.group(1).trim();
|
||||
String allowedIp = matcher.group(2).trim();
|
||||
|
||||
String vpnIp = allowedIp.replace("/32", "");
|
||||
|
||||
Router router = routerRepository
|
||||
.findByWireguardPublicKey(publicKey)
|
||||
.orElse(null);
|
||||
|
||||
if (router == null) {
|
||||
|
||||
Router imported = Router.importedVpnRouter(
|
||||
publicKey,
|
||||
vpnIp
|
||||
);
|
||||
|
||||
routerRepository.save(imported);
|
||||
|
||||
created++;
|
||||
|
||||
} else {
|
||||
|
||||
router.syncVpnPeer(publicKey, vpnIp);
|
||||
|
||||
routerRepository.save(router);
|
||||
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
return new SyncRoutersFromVpsResponse(
|
||||
lines.size(),
|
||||
created,
|
||||
updated
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.litoralregas.vpnprovisioner.router.startup;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.router.service.RouterSyncService;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class RouterStartupSync implements ApplicationRunner {
|
||||
|
||||
private final RouterSyncService routerSyncService;
|
||||
|
||||
public RouterStartupSync(RouterSyncService routerSyncService) {
|
||||
this.routerSyncService = routerSyncService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
|
||||
routerSyncService.syncFromVps();
|
||||
|
||||
System.out.println("Router VPS sync completed.");
|
||||
}
|
||||
}
|
||||
@@ -93,4 +93,18 @@ public class WireGuardVpsService {
|
||||
|
||||
return result.stdout();
|
||||
}
|
||||
|
||||
public String showAllowedIps() {
|
||||
SshCommandResult result = sshService.executeOnConfiguredVps(
|
||||
"sudo /usr/local/sbin/lr-wg-used-ips"
|
||||
);
|
||||
|
||||
if (result.exitCode() != 0) {
|
||||
throw new SshCommandException(
|
||||
"Failed to query WireGuard allowed IPs: " + result.stderr()
|
||||
);
|
||||
}
|
||||
|
||||
return result.stdout();
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.controller;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.wireguard.dto.CreateWireGuardPeerRequest;
|
||||
import com.litoralregas.vpnprovisioner.wireguard.dto.WireGuardPeerResponse;
|
||||
import com.litoralregas.vpnprovisioner.wireguard.service.WireGuardPeerService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/routers/{routerId}/wireguard-peer")
|
||||
public class WireGuardPeerController {
|
||||
|
||||
private final WireGuardPeerService wireGuardPeerService;
|
||||
|
||||
public WireGuardPeerController(WireGuardPeerService wireGuardPeerService) {
|
||||
this.wireGuardPeerService = wireGuardPeerService;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public WireGuardPeerResponse create(
|
||||
@PathVariable UUID routerId,
|
||||
@Valid @RequestBody CreateWireGuardPeerRequest request
|
||||
) {
|
||||
return wireGuardPeerService.createForRouter(routerId, request);
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.dto;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.wireguard.entity.EndpointMode;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record CreateWireGuardPeerRequest(
|
||||
@NotBlank String publicKey,
|
||||
@NotNull EndpointMode endpointMode
|
||||
) {
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.dto;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.wireguard.entity.EndpointMode;
|
||||
import com.litoralregas.vpnprovisioner.wireguard.entity.WireGuardPeerStatus;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
public record WireGuardPeerResponse(
|
||||
UUID id,
|
||||
UUID routerId,
|
||||
String publicKey,
|
||||
String vpnIp,
|
||||
String allowedIps,
|
||||
EndpointMode endpointMode,
|
||||
WireGuardPeerStatus status,
|
||||
Instant createdAt,
|
||||
Instant updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.entity;
|
||||
|
||||
public enum EndpointMode {
|
||||
NORMAL_WIREGUARD,
|
||||
UDP2RAW
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "wireguard_peers")
|
||||
public class WireGuardPeer {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private UUID routerId;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private String publicKey;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String vpnIp;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String allowedIps;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private EndpointMode endpointMode;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private WireGuardPeerStatus status;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Instant updatedAt;
|
||||
|
||||
protected WireGuardPeer() {
|
||||
}
|
||||
|
||||
public WireGuardPeer(UUID routerId, String publicKey, String vpnIp, EndpointMode endpointMode) {
|
||||
this.id = UUID.randomUUID();
|
||||
this.routerId = routerId;
|
||||
this.publicKey = publicKey;
|
||||
this.vpnIp = vpnIp;
|
||||
this.allowedIps = vpnIp + "/32";
|
||||
this.endpointMode = endpointMode;
|
||||
this.status = WireGuardPeerStatus.PENDING_APPLY;
|
||||
this.createdAt = Instant.now();
|
||||
this.updatedAt = Instant.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
this.updatedAt = Instant.now();
|
||||
}
|
||||
|
||||
public UUID getId() { return id; }
|
||||
public UUID getRouterId() { return routerId; }
|
||||
public String getPublicKey() { return publicKey; }
|
||||
public String getVpnIp() { return vpnIp; }
|
||||
public String getAllowedIps() { return allowedIps; }
|
||||
public EndpointMode getEndpointMode() { return endpointMode; }
|
||||
public WireGuardPeerStatus getStatus() { return status; }
|
||||
public Instant getCreatedAt() { return createdAt; }
|
||||
public Instant getUpdatedAt() { return updatedAt; }
|
||||
|
||||
public void markApplied() {
|
||||
this.status = WireGuardPeerStatus.APPLIED;
|
||||
this.updatedAt = Instant.now();
|
||||
}
|
||||
|
||||
public void markFailed() {
|
||||
this.status = WireGuardPeerStatus.FAILED;
|
||||
this.updatedAt = Instant.now();
|
||||
}
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.entity;
|
||||
|
||||
public enum WireGuardPeerStatus {
|
||||
PENDING_APPLY,
|
||||
APPLIED,
|
||||
FAILED,
|
||||
REVOKED
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.repository;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.wireguard.entity.WireGuardPeer;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface WireGuardPeerRepository extends JpaRepository<WireGuardPeer, UUID> {
|
||||
|
||||
Optional<WireGuardPeer> findByRouterId(UUID routerId);
|
||||
|
||||
boolean existsByPublicKey(String publicKey);
|
||||
}
|
||||
-93
@@ -1,93 +0,0 @@
|
||||
package com.litoralregas.vpnprovisioner.wireguard.service;
|
||||
|
||||
import com.litoralregas.vpnprovisioner.common.exception.ResourceNotFoundException;
|
||||
import com.litoralregas.vpnprovisioner.ipam.dto.VpnIpAllocationResponse;
|
||||
import com.litoralregas.vpnprovisioner.ipam.service.VpnIpAllocationService;
|
||||
import com.litoralregas.vpnprovisioner.router.repository.RouterRepository;
|
||||
import com.litoralregas.vpnprovisioner.vps.WireGuardVpsService;
|
||||
import com.litoralregas.vpnprovisioner.wireguard.dto.CreateWireGuardPeerRequest;
|
||||
import com.litoralregas.vpnprovisioner.wireguard.dto.WireGuardPeerResponse;
|
||||
import com.litoralregas.vpnprovisioner.wireguard.entity.WireGuardPeer;
|
||||
import com.litoralregas.vpnprovisioner.wireguard.repository.WireGuardPeerRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class WireGuardPeerService {
|
||||
|
||||
private final WireGuardPeerRepository wireGuardPeerRepository;
|
||||
private final RouterRepository routerRepository;
|
||||
private final VpnIpAllocationService vpnIpAllocationService;
|
||||
private final WireGuardVpsService wireGuardVpsService;
|
||||
|
||||
public WireGuardPeerService(
|
||||
WireGuardPeerRepository wireGuardPeerRepository,
|
||||
RouterRepository routerRepository,
|
||||
VpnIpAllocationService vpnIpAllocationService,
|
||||
WireGuardVpsService wireGuardVpsService) {
|
||||
this.wireGuardPeerRepository = wireGuardPeerRepository;
|
||||
this.routerRepository = routerRepository;
|
||||
this.vpnIpAllocationService = vpnIpAllocationService;
|
||||
this.wireGuardVpsService = wireGuardVpsService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WireGuardPeerResponse createForRouter(UUID routerId, CreateWireGuardPeerRequest request) {
|
||||
routerRepository.findById(routerId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Router not found: " + routerId));
|
||||
|
||||
return wireGuardPeerRepository.findByRouterId(routerId)
|
||||
.map(this::toResponse)
|
||||
.orElseGet(() -> createNewPeer(routerId, request));
|
||||
}
|
||||
|
||||
private WireGuardPeerResponse createNewPeer(UUID routerId, CreateWireGuardPeerRequest request) {
|
||||
if (wireGuardPeerRepository.existsByPublicKey(request.publicKey())) {
|
||||
throw new IllegalArgumentException("WireGuard public key already exists");
|
||||
}
|
||||
|
||||
VpnIpAllocationResponse allocation =
|
||||
vpnIpAllocationService.allocateForRouter(routerId);
|
||||
|
||||
WireGuardPeer peer = new WireGuardPeer(
|
||||
routerId,
|
||||
request.publicKey(),
|
||||
allocation.ipAddress(),
|
||||
request.endpointMode()
|
||||
);
|
||||
|
||||
WireGuardPeer saved = wireGuardPeerRepository.save(peer);
|
||||
|
||||
|
||||
try {
|
||||
wireGuardVpsService.applyPeer(
|
||||
saved.getPublicKey(),
|
||||
saved.getAllowedIps()
|
||||
);
|
||||
|
||||
saved.markApplied();
|
||||
|
||||
} catch (RuntimeException exception) {
|
||||
saved.markFailed();
|
||||
throw exception;
|
||||
}
|
||||
|
||||
return toResponse(saved);
|
||||
}
|
||||
|
||||
private WireGuardPeerResponse toResponse(WireGuardPeer peer) {
|
||||
return new WireGuardPeerResponse(
|
||||
peer.getId(),
|
||||
peer.getRouterId(),
|
||||
peer.getPublicKey(),
|
||||
peer.getVpnIp(),
|
||||
peer.getAllowedIps(),
|
||||
peer.getEndpointMode(),
|
||||
peer.getStatus(),
|
||||
peer.getCreatedAt(),
|
||||
peer.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS wireguard_peers;
|
||||
DROP TABLE IF EXISTS vpn_ip_allocations;
|
||||
Reference in New Issue
Block a user