Adds Download endpoint for client bundle

This commit is contained in:
litoral05
2026-05-05 15:47:24 +01:00
parent 78cb539508
commit 1db35b9088
5 changed files with 117 additions and 4 deletions
@@ -0,0 +1,10 @@
package com.litoralregas.openvpn.openvpn;
import java.io.InputStream;
public record OpenVpnBundleDownload(
String filename,
long size,
InputStream inputStream
) {
}
@@ -137,4 +137,13 @@ public class OpenVpnService {
return sshService.executeOnConfiguredVps(command); return sshService.executeOnConfiguredVps(command);
} }
public OpenVpnBundleDownload downloadClientBundle(String clientName) {
String filename = clientName + ".tar.gz";
return sshService.downloadFileFromConfiguredVps(
"/var/litoral_regas_openvpn/clients/" + filename,
filename
);
}
} }
@@ -7,6 +7,10 @@ import com.litoralregas.openvpn.openvpn.IpAllocationService;
import com.litoralregas.openvpn.openvpn.OpenVpnService; import com.litoralregas.openvpn.openvpn.OpenVpnService;
import com.litoralregas.openvpn.ssh.SshService; import com.litoralregas.openvpn.ssh.SshService;
import jakarta.validation.Valid; 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 org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
@@ -18,14 +22,12 @@ public class RouterController {
private final RouterService service; private final RouterService service;
private final DeploymentService deploymentService; private final DeploymentService deploymentService;
private final SshService sshService;
private final IpAllocationService ipAllocationService; private final IpAllocationService ipAllocationService;
private final OpenVpnService openVpnService; 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.service = service;
this.deploymentService = deploymentService; this.deploymentService = deploymentService;
this.sshService = sshService;
this.ipAllocationService = ipAllocationService; this.ipAllocationService = ipAllocationService;
this.openVpnService = openVpnService; this.openVpnService = openVpnService;
} }
@@ -45,7 +47,6 @@ public class RouterController {
return service.findById(id); return service.findById(id);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public void delete(@PathVariable UUID id) { public void delete(@PathVariable UUID id) {
service.delete(id); service.delete(id);
@@ -154,4 +155,28 @@ public class RouterController {
return DeploymentResponse.from(failed); return DeploymentResponse.from(failed);
} }
} }
@GetMapping("/{id}/bundle")
public ResponseEntity<InputStreamResource> 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()));
}
} }
@@ -1,14 +1,19 @@
package com.litoralregas.openvpn.ssh; package com.litoralregas.openvpn.ssh;
import com.jcraft.jsch.*; import com.jcraft.jsch.*;
import com.litoralregas.openvpn.openvpn.OpenVpnBundleDownload;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@Service @Service
public class SshService { public class SshService {
private final VpsSshProperties properties; private final VpsSshProperties properties;
public SshService(VpsSshProperties properties) { public SshService(VpsSshProperties properties) {
this.properties = 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( public SshCommandResult execute(
String host, String host,
int port, int port,
Binary file not shown.