Add safe OpenVPN preflight and dry-run provisioning
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
if (openVpnService.isDryRun()) {
|
||||
service.forceStatus(id, RouterStatus.PENDING);
|
||||
} else {
|
||||
service.forceStatus(id, RouterStatus.PROVISIONED);
|
||||
}
|
||||
|
||||
return DeploymentResponse.from(finishedDeployment);
|
||||
} catch (Exception exception) {
|
||||
|
||||
@@ -34,3 +34,4 @@ lr:
|
||||
|
||||
openvpn:
|
||||
tools-path: ${LR_OPENVPN_TOOLS_PATH:/var/litoral_regas_openvpn/tools}
|
||||
provision-dry-run: ${LR_OPENVPN_PROVISION_DRY_RUN:true}
|
||||
Reference in New Issue
Block a user