diff --git a/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnBundleDownload.java b/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnBundleDownload.java new file mode 100644 index 0000000..6094cb5 --- /dev/null +++ b/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnBundleDownload.java @@ -0,0 +1,10 @@ +package com.litoralregas.openvpn.openvpn; + +import java.io.InputStream; + +public record OpenVpnBundleDownload( + String filename, + long size, + InputStream inputStream +) { +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnService.java b/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnService.java index 7677d91..2d76f6a 100644 --- a/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnService.java +++ b/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnService.java @@ -137,4 +137,13 @@ public class OpenVpnService { return sshService.executeOnConfiguredVps(command); } + + public OpenVpnBundleDownload downloadClientBundle(String clientName) { + String filename = clientName + ".tar.gz"; + + return sshService.downloadFileFromConfiguredVps( + "/var/litoral_regas_openvpn/clients/" + filename, + filename + ); + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/openvpn/router/RouterController.java b/src/main/java/com/litoralregas/openvpn/router/RouterController.java index f969d3d..44f94b5 100644 --- a/src/main/java/com/litoralregas/openvpn/router/RouterController.java +++ b/src/main/java/com/litoralregas/openvpn/router/RouterController.java @@ -7,6 +7,10 @@ import com.litoralregas.openvpn.openvpn.IpAllocationService; import com.litoralregas.openvpn.openvpn.OpenVpnService; import com.litoralregas.openvpn.ssh.SshService; import jakarta.validation.Valid; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -18,14 +22,12 @@ public class RouterController { private final RouterService service; private final DeploymentService deploymentService; - private final SshService sshService; private final IpAllocationService ipAllocationService; private final OpenVpnService openVpnService; - public RouterController(RouterService service, DeploymentService deploymentService, SshService sshService, IpAllocationService ipAllocationService, OpenVpnService openVpnService) { + public RouterController(RouterService service, DeploymentService deploymentService, IpAllocationService ipAllocationService, OpenVpnService openVpnService) { this.service = service; this.deploymentService = deploymentService; - this.sshService = sshService; this.ipAllocationService = ipAllocationService; this.openVpnService = openVpnService; } @@ -45,7 +47,6 @@ public class RouterController { return service.findById(id); } - @DeleteMapping("/{id}") public void delete(@PathVariable UUID id) { service.delete(id); @@ -154,4 +155,28 @@ public class RouterController { return DeploymentResponse.from(failed); } } + + @GetMapping("/{id}/bundle") + public ResponseEntity downloadBundle(@PathVariable UUID id) { + Router router = service.findById(id); + + if (router.getStatus() != RouterStatus.PROVISIONED) { + throw new IllegalStateException("Router is not provisioned yet"); + } + + var allocation = ipAllocationService.findByRouterId(id); + + var bundle = openVpnService.downloadClientBundle( + allocation.getClientName() + ); + + return ResponseEntity.ok() + .header( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + bundle.filename() + "\"" + ) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentLength(bundle.size()) + .body(new InputStreamResource(bundle.inputStream())); + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/openvpn/ssh/SshService.java b/src/main/java/com/litoralregas/openvpn/ssh/SshService.java index 31151e2..69e9021 100644 --- a/src/main/java/com/litoralregas/openvpn/ssh/SshService.java +++ b/src/main/java/com/litoralregas/openvpn/ssh/SshService.java @@ -1,14 +1,19 @@ package com.litoralregas.openvpn.ssh; import com.jcraft.jsch.*; +import com.litoralregas.openvpn.openvpn.OpenVpnBundleDownload; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; @Service public class SshService { private final VpsSshProperties properties; + public SshService(VpsSshProperties properties) { this.properties = properties; } @@ -23,6 +28,70 @@ public class SshService { ); } + public OpenVpnBundleDownload downloadFileFromConfiguredVps( + String remotePath, + String filename + ) { + return downloadFile( + properties.getHost(), + properties.getPort(), + properties.getUsername(), + properties.getPassword(), + remotePath, + filename + ); + } + + public OpenVpnBundleDownload downloadFile( + String host, + int port, + String username, + String password, + String remotePath, + String filename + ) { + try { + JSch jsch = new JSch(); + + Session session = jsch.getSession(username, host, port); + session.setPassword(password); + session.setConfig("StrictHostKeyChecking", "no"); + session.connect(10_000); + + ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); + channel.connect(10_000); + + SftpATTRS attrs = channel.lstat(remotePath); + long size = attrs.getSize(); + + InputStream inputStream = channel.get(remotePath); + + InputStream wrapped = new FilterInputStream(inputStream) { + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + channel.disconnect(); + session.disconnect(); + } + } + }; + + return new OpenVpnBundleDownload( + filename, + size, + wrapped + ); + + } catch (Exception e) { + throw new IllegalStateException( + "Failed to download file from VPS: " + remotePath, + e + ); + } + } + public SshCommandResult execute( String host, int port, diff --git a/test-router.tar.gz b/test-router.tar.gz new file mode 100644 index 0000000..e50ff2a Binary files /dev/null and b/test-router.tar.gz differ