mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: add audit
This commit is contained in:
@@ -39,6 +39,7 @@ export * from "./setup/config-paths";
|
||||
export * from "./setup/postgres-setup";
|
||||
export * from "./setup/redis-setup";
|
||||
export * from "./setup/server-setup";
|
||||
export * from "./setup/server-security";
|
||||
export * from "./setup/setup";
|
||||
export * from "./setup/traefik-setup";
|
||||
export * from "./setup/server-validate";
|
||||
|
||||
149
packages/server/src/setup/server-audit.ts
Normal file
149
packages/server/src/setup/server-audit.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { Client } from "ssh2";
|
||||
import { findServerById } from "../services/server";
|
||||
|
||||
const validateUfw = () => `
|
||||
if command -v ufw >/dev/null 2>&1; then
|
||||
isInstalled=true
|
||||
isActive=$(sudo ufw status | grep -q "Status: active" && echo true || echo false)
|
||||
defaultIncoming=$(sudo ufw status verbose | grep "Default:" | grep "incoming" | awk '{print $2}')
|
||||
echo "{\\"installed\\": $isInstalled, \\"active\\": $isActive, \\"defaultIncoming\\": \\"$defaultIncoming\\"}"
|
||||
else
|
||||
echo "{\\"installed\\": false, \\"active\\": false, \\"defaultIncoming\\": \\"unknown\\"}"
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateSsh = () => `
|
||||
if systemctl is-active --quiet sshd; then
|
||||
isEnabled=true
|
||||
hasKeyAuth=$(find "$HOME/.ssh" -type f -name "authorized_keys" 2>/dev/null | grep -q . && echo true || echo false)
|
||||
permitRootLogin=$(sudo sshd -T | grep -i "^PermitRootLogin" | awk '{print $2}')
|
||||
passwordAuth=$(sudo sshd -T | grep -i "^PasswordAuthentication" | awk '{print $2}')
|
||||
usePam=$(sudo sshd -T | grep -i "^UsePAM" | awk '{print $2}')
|
||||
echo "{\\"enabled\\": $isEnabled, \\"keyAuth\\": $hasKeyAuth, \\"permitRootLogin\\": \\"$permitRootLogin\\", \\"passwordAuth\\": \\"$passwordAuth\\", \\"usePam\\": \\"$usePam\\"}"
|
||||
else
|
||||
echo "{\\"enabled\\": false, \\"keyAuth\\": false, \\"permitRootLogin\\": \\"unknown\\", \\"passwordAuth\\": \\"unknown\\", \\"usePam\\": \\"unknown\\"}"
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateNonRootUser = () => `
|
||||
sudoUsers=$(grep -Po '^sudo:.*:\\K.*$' /etc/group | tr ',' '\\n' | grep -v root)
|
||||
adminUsers=$(grep -Po '^admin:.*:\\K.*$' /etc/group | tr ',' '\\n' | grep -v root)
|
||||
privilegedUsers=$(echo -e "${sudoUsers}\\n${adminUsers}" | sort -u | grep -v '^$')
|
||||
validUserFound=false
|
||||
|
||||
while IFS= read -r user; do
|
||||
userShell=$(getent passwd "$user" | cut -d: -f7)
|
||||
if [[ "$userShell" != "/usr/sbin/nologin" && "$userShell" != "/bin/false" ]]; then
|
||||
validUserFound=true
|
||||
break
|
||||
fi
|
||||
done <<< "$privilegedUsers"
|
||||
|
||||
echo "{\\"hasValidSudoUser\\": $validUserFound}"
|
||||
`;
|
||||
|
||||
const validateUnattendedUpgrades = () => `
|
||||
if dpkg -l | grep -q "unattended-upgrades"; then
|
||||
isInstalled=true
|
||||
isActive=$(systemctl is-active --quiet unattended-upgrades.service && echo true || echo false)
|
||||
|
||||
if [ -f "/etc/apt/apt.conf.d/20auto-upgrades" ]; then
|
||||
updateEnabled=$(grep "APT::Periodic::Update-Package-Lists" "/etc/apt/apt.conf.d/20auto-upgrades" | grep -o '[0-9]\\+' || echo "0")
|
||||
upgradeEnabled=$(grep "APT::Periodic::Unattended-Upgrade" "/etc/apt/apt.conf.d/20auto-upgrades" | grep -o '[0-9]\\+' || echo "0")
|
||||
echo "{\\"installed\\": $isInstalled, \\"active\\": $isActive, \\"updateEnabled\\": $updateEnabled, \\"upgradeEnabled\\": $upgradeEnabled}"
|
||||
else
|
||||
echo "{\\"installed\\": $isInstalled, \\"active\\": $isActive, \\"updateEnabled\\": 0, \\"upgradeEnabled\\": 0}"
|
||||
fi
|
||||
else
|
||||
echo "{\\"installed\\": false, \\"active\\": false, \\"updateEnabled\\": 0, \\"upgradeEnabled\\": 0}"
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateFail2ban = () => `
|
||||
if dpkg -l | grep -q "fail2ban"; then
|
||||
isInstalled=true
|
||||
isEnabled=$(systemctl is-enabled --quiet fail2ban.service && echo true || echo false)
|
||||
isActive=$(systemctl is-active --quiet fail2ban.service && echo true || echo false)
|
||||
|
||||
if [ -f "/etc/fail2ban/jail.local" ]; then
|
||||
sshEnabled=$(grep -A10 "^\\[sshd\\]" /etc/fail2ban/jail.local | grep "enabled" | awk '{print $NF}' | tr -d '[:space:]')
|
||||
sshMode=$(grep -A10 "^\\[sshd\\]" /etc/fail2ban/jail.local | grep "^mode[[:space:]]*=[[:space:]]*aggressive" >/dev/null && echo "aggressive" || echo "normal")
|
||||
echo "{\\"installed\\": $isInstalled, \\"enabled\\": $isEnabled, \\"active\\": $isActive, \\"sshEnabled\\": \\"$sshEnabled\\", \\"sshMode\\": \\"$sshMode\\"}"
|
||||
else
|
||||
echo "{\\"installed\\": $isInstalled, \\"enabled\\": $isEnabled, \\"active\\": $isActive, \\"sshEnabled\\": \\"false\\", \\"sshMode\\": \\"normal\\"}"
|
||||
fi
|
||||
else
|
||||
echo "{\\"installed\\": false, \\"enabled\\": false, \\"active\\": false, \\"sshEnabled\\": \\"false\\", \\"sshMode\\": \\"normal\\"}"
|
||||
fi
|
||||
`;
|
||||
|
||||
export const serverAudit = async (serverId: string) => {
|
||||
const client = new Client();
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) {
|
||||
throw new Error("No SSH Key found");
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
client
|
||||
.once("ready", () => {
|
||||
const bashCommand = `
|
||||
command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
ufwStatus=$(${validateUfw()})
|
||||
sshStatus=$(${validateSsh()})
|
||||
nonRootStatus=$(${validateNonRootUser()})
|
||||
upgradesStatus=$(${validateUnattendedUpgrades()})
|
||||
fail2banStatus=$(${validateFail2ban()})
|
||||
|
||||
echo "{\\"ufw\\": $ufwStatus, \\"ssh\\": $sshStatus, \\"nonRootUser\\": $nonRootStatus, \\"unattendedUpgrades\\": $upgradesStatus, \\"fail2ban\\": $fail2banStatus}"
|
||||
`;
|
||||
|
||||
client.exec(bashCommand, (err, stream) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
let output = "";
|
||||
stream
|
||||
.on("close", () => {
|
||||
client.end();
|
||||
try {
|
||||
const result = JSON.parse(output.trim());
|
||||
resolve(result);
|
||||
} catch (parseError) {
|
||||
reject(
|
||||
new Error(
|
||||
`Failed to parse output: ${parseError instanceof Error ? parseError.message : parseError}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
})
|
||||
.on("data", (data: string) => {
|
||||
output += data;
|
||||
})
|
||||
.stderr.on("data", (data) => {});
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
client.end();
|
||||
if (err.level === "client-authentication") {
|
||||
reject(
|
||||
new Error(
|
||||
`Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
reject(new Error(`SSH connection error: ${err.message}`));
|
||||
}
|
||||
})
|
||||
.connect({
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
});
|
||||
});
|
||||
};
|
||||
150
packages/server/src/setup/server-security.ts
Normal file
150
packages/server/src/setup/server-security.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Client } from "ssh2";
|
||||
import { findServerById } from "../services/server";
|
||||
|
||||
const validateDocker = () => `
|
||||
if command_exists docker; then
|
||||
echo "$(docker --version | awk '{print $3}' | sed 's/,//') true"
|
||||
else
|
||||
echo "0.0.0 false"
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateRClone = () => `
|
||||
if command_exists rclone; then
|
||||
echo "$(rclone --version | head -n 1 | awk '{print $2}' | sed 's/^v//') true"
|
||||
else
|
||||
echo "0.0.0 false"
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateSwarm = () => `
|
||||
if docker info --format '{{.Swarm.LocalNodeState}}' | grep -q 'active'; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateNixpacks = () => `
|
||||
if command_exists nixpacks; then
|
||||
version=$(nixpacks --version | awk '{print $2}')
|
||||
if [ -n "$version" ]; then
|
||||
echo "$version true"
|
||||
else
|
||||
echo "0.0.0 false"
|
||||
fi
|
||||
else
|
||||
echo "0.0.0 false"
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateBuildpacks = () => `
|
||||
if command_exists pack; then
|
||||
version=$(pack --version | awk '{print $1}')
|
||||
if [ -n "$version" ]; then
|
||||
echo "$version true"
|
||||
else
|
||||
echo "0.0.0 false"
|
||||
fi
|
||||
else
|
||||
echo "0.0.0 false"
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateMainDirectory = () => `
|
||||
if [ -d "/etc/dokploy" ]; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
`;
|
||||
|
||||
const validateDokployNetwork = () => `
|
||||
if docker network ls | grep -q 'dokploy-network'; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
`;
|
||||
|
||||
export const serverSecurity = async (serverId: string) => {
|
||||
const client = new Client();
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) {
|
||||
throw new Error("No SSH Key found");
|
||||
}
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
client
|
||||
.once("ready", () => {
|
||||
const bashCommand = `
|
||||
set -u;
|
||||
check_os() {
|
||||
if [ -f /etc/lsb-release ]; then
|
||||
echo "ubuntu"
|
||||
elif [ -f /etc/debian_version ]; then
|
||||
echo "debian"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
os=$(check_os)
|
||||
|
||||
if [ -z "$os" ]; then
|
||||
echo "This script only supports Ubuntu/Debian systems. Exiting."
|
||||
echo "Please ensure you're running this script on a supported operating system."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Detected supported OS: $os"
|
||||
echo "Installing requirements for OS: $os"
|
||||
`;
|
||||
client.exec(bashCommand, (err, stream) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
let output = "";
|
||||
stream
|
||||
.on("close", () => {
|
||||
client.end();
|
||||
try {
|
||||
// const result = JSON.parse(output.trim());
|
||||
console.log("Output:", output);
|
||||
resolve(output.trim());
|
||||
} catch (parseError) {
|
||||
reject(
|
||||
new Error(
|
||||
`Failed to parse output: ${parseError instanceof Error ? parseError.message : parseError}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
})
|
||||
.on("data", (data: string) => {
|
||||
output += data;
|
||||
})
|
||||
.stderr.on("data", (data) => {});
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
client.end();
|
||||
if (err.level === "client-authentication") {
|
||||
reject(
|
||||
new Error(
|
||||
`Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
reject(new Error(`SSH connection error: ${err.message}`));
|
||||
}
|
||||
})
|
||||
.connect({
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
});
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user