From 40ae52f00e57eaac512a9fb090be7dc01300976a Mon Sep 17 00:00:00 2001 From: litoral05 Date: Tue, 5 May 2026 14:05:21 +0100 Subject: [PATCH] Add safe OpenVPN preflight and dry-run provisioning --- .../openvpn/openvpn/OpenVpnProperties.java | 8 +++++ .../openvpn/openvpn/OpenVpnService.java | 31 +++++++++++++++++++ .../openvpn/router/RouterController.java | 28 ++++++++++++++--- src/main/resources/application.yaml | 3 +- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnProperties.java b/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnProperties.java index 9b8245f..3645017 100644 --- a/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnProperties.java +++ b/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnProperties.java @@ -6,6 +6,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class OpenVpnProperties { private String toolsPath; + private boolean provisionDryRun = true; public String getToolsPath() { return toolsPath; @@ -14,4 +15,11 @@ public class OpenVpnProperties { public void setToolsPath(String toolsPath) { this.toolsPath = toolsPath; } + public boolean isProvisionDryRun() { + return provisionDryRun; + } + + public void setProvisionDryRun(boolean provisionDryRun) { + this.provisionDryRun = provisionDryRun; + } } \ 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 08348c8..cd539bb 100644 --- a/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnService.java +++ b/src/main/java/com/litoralregas/openvpn/openvpn/OpenVpnService.java @@ -1,5 +1,6 @@ package com.litoralregas.openvpn.openvpn; +import com.litoralregas.openvpn.ssh.SshCommandResult; import com.litoralregas.openvpn.ssh.SshService; import org.springframework.stereotype.Service; @@ -82,4 +83,34 @@ public class OpenVpnService { throw new IllegalArgumentException("Unsafe command value: " + value); } } + + public SshCommandResult runPreflightCheck() { + return sshService.executeOnConfiguredVps( + "test -x " + properties.getToolsPath() + "/provision-client.sh && " + + "test -x " + properties.getToolsPath() + "/remove-client.sh && " + + "test -x " + properties.getToolsPath() + "/list-clients.sh && " + + "test -d /etc/openvpn/easy-rsa && " + + "test -d /etc/openvpn/server/ccd && " + + "test -f /etc/openvpn/server/server.conf && " + + "systemctl is-active openvpn-server@server" + ); + } + + public SshCommandResult provisionClient(String clientName, String lanSubnet, String vpnIp) { + String command = buildProvisionCommand(clientName, lanSubnet, vpnIp); + + if (properties.isProvisionDryRun()) { + return new SshCommandResult( + 0, + "DRY RUN ONLY. Would execute: " + command, + "" + ); + } + + return sshService.executeOnConfiguredVps(command); + } + + public boolean isDryRun() { + return properties.isProvisionDryRun(); + } } \ 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 597225c..a60d64a 100644 --- a/src/main/java/com/litoralregas/openvpn/router/RouterController.java +++ b/src/main/java/com/litoralregas/openvpn/router/RouterController.java @@ -69,20 +69,38 @@ public class RouterController { var allocation = ipAllocationService.findByRouterId(id); - String command = openVpnService.buildProvisionCommand( + var preflight = openVpnService.runPreflightCheck(); + + if (preflight.exitCode() != 0) { + throw new IllegalStateException( + "Preflight failed. stdout: " + preflight.stdout() + + " stderr: " + preflight.stderr() + ); + } + + var result = openVpnService.provisionClient( allocation.getClientName(), allocation.getLanSubnet(), allocation.getVpnIp() ); + if (result.exitCode() != 0) { + throw new IllegalStateException( + "Provision failed. stdout: " + result.stdout() + + " stderr: " + result.stderr() + ); + } + var finishedDeployment = deploymentService.finishSuccess( deployment, - "DRY RUN ONLY. Would execute: " + command + result.stdout() ); - // Keep this as PROVISIONING? No — dry run succeeded but real provision did not happen. - // So we should NOT mark as PROVISIONED yet. - service.forceStatus(id, RouterStatus.PENDING); + if (openVpnService.isDryRun()) { + service.forceStatus(id, RouterStatus.PENDING); + } else { + service.forceStatus(id, RouterStatus.PROVISIONED); + } return DeploymentResponse.from(finishedDeployment); } catch (Exception exception) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 88c4dc5..87c836d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -33,4 +33,5 @@ lr: password: ${LR_VPS_SSH_PASSWORD} openvpn: - tools-path: ${LR_OPENVPN_TOOLS_PATH:/var/litoral_regas_openvpn/tools} \ No newline at end of file + tools-path: ${LR_OPENVPN_TOOLS_PATH:/var/litoral_regas_openvpn/tools} + provision-dry-run: ${LR_OPENVPN_PROVISION_DRY_RUN:true} \ No newline at end of file