mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add security audit
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Loader2, PcCase, RefreshCw } from "lucide-react";
|
import { Loader2, LockKeyhole, PcCase, RefreshCw } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { StatusRow } from "./gpu-support";
|
import { StatusRow } from "./gpu-support";
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ export const SecuritySetup = ({ serverId }: Props) => {
|
|||||||
<div className="flex flex-row gap-2 justify-between w-full max-sm:flex-col">
|
<div className="flex flex-row gap-2 justify-between w-full max-sm:flex-col">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<PcCase className="size-5" />
|
<LockKeyhole className="size-5" />
|
||||||
<CardTitle className="text-xl">
|
<CardTitle className="text-xl">
|
||||||
Setup Security Sugestions
|
Setup Security Sugestions
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -42,11 +42,11 @@ export const SecuritySetup = ({ serverId }: Props) => {
|
|||||||
<CardDescription>Check the security sugestions</CardDescription>
|
<CardDescription>Check the security sugestions</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
// isLoading={isRefreshing}
|
isLoading={isRefreshing}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
await refetch();
|
await refetch();
|
||||||
// setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RefreshCw className="size-4" />
|
<RefreshCw className="size-4" />
|
||||||
@@ -71,72 +71,149 @@ export const SecuritySetup = ({ serverId }: Props) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="grid w-full gap-4">
|
<div className="grid w-full gap-4">
|
||||||
<div className="border rounded-lg p-4">
|
<div className="border rounded-lg p-4">
|
||||||
<h3 className="text-lg font-semibold mb-1">Status</h3>
|
<h3 className="text-lg font-semibold mb-1">UFW</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Shows the server configuration status
|
UFW (Uncomplicated Firewall) is a simple firewall that can
|
||||||
|
be used to block incoming and outgoing traffic from your
|
||||||
|
server.
|
||||||
</p>
|
</p>
|
||||||
<div className="grid gap-2.5">
|
<div className="grid gap-2.5">
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Docker Installed"
|
label="UFW Installed"
|
||||||
isEnabled={data?.docker?.enabled}
|
isEnabled={data?.ufw?.installed}
|
||||||
description={
|
description={
|
||||||
data?.docker?.enabled
|
data?.ufw?.installed
|
||||||
? `Installed: ${data?.docker?.version}`
|
? "Installed (Recommended)"
|
||||||
: undefined
|
: "Not Installed (UFW should be installed for security)"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="RClone Installed"
|
label="Status"
|
||||||
isEnabled={data?.rclone?.enabled}
|
isEnabled={data?.ufw?.active}
|
||||||
description={
|
description={
|
||||||
data?.rclone?.enabled
|
data?.ufw?.active
|
||||||
? `Installed: ${data?.rclone?.version}`
|
? "Active (Recommended)"
|
||||||
: undefined
|
: "Not Active (UFW should be enabled for security)"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Nixpacks Installed"
|
label="Default Incoming"
|
||||||
isEnabled={data?.nixpacks?.enabled}
|
isEnabled={data?.ufw?.defaultIncoming === "deny"}
|
||||||
description={
|
description={
|
||||||
data?.nixpacks?.enabled
|
data?.ufw?.defaultIncoming === "deny"
|
||||||
? `Installed: ${data?.nixpacks?.version}`
|
? "Default: Deny (Recommended)"
|
||||||
: undefined
|
: `Default: ${data?.ufw?.defaultIncoming} (Should be set to 'deny' for security)`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border rounded-lg p-4">
|
||||||
|
<h3 className="text-lg font-semibold mb-1">SSH</h3>
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
SSH (Secure Shell) is a protocol that allows you to securely
|
||||||
|
connect to a server and execute commands on it.
|
||||||
|
</p>
|
||||||
|
<div className="grid gap-2.5">
|
||||||
|
<StatusRow
|
||||||
|
label="Enabled"
|
||||||
|
isEnabled={data?.ssh.enabled}
|
||||||
|
description={
|
||||||
|
data?.ssh.enabled ? "Enabled" : "Not Enabled (SSH should be enabled)"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Buildpacks Installed"
|
label="Key Auth"
|
||||||
isEnabled={data?.buildpacks?.enabled}
|
isEnabled={data?.ssh.keyAuth}
|
||||||
description={
|
description={
|
||||||
data?.buildpacks?.enabled
|
data?.ssh.keyAuth
|
||||||
? `Installed: ${data?.buildpacks?.version}`
|
? "Enabled (Recommended)"
|
||||||
: undefined
|
: "Not Enabled (Key Authentication should be enabled)"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Docker Swarm Initialized"
|
label="Password Auth"
|
||||||
isEnabled={data?.isSwarmInstalled}
|
isEnabled={data?.ssh.passwordAuth === "no"}
|
||||||
description={
|
description={
|
||||||
data?.isSwarmInstalled
|
data?.ssh.passwordAuth === "no"
|
||||||
? "Initialized"
|
? "Disabled (Recommended)"
|
||||||
: "Not Initialized"
|
: "Enabled (Password Authentication should be disabled)"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Dokploy Network Created"
|
label="Permit Root Login"
|
||||||
isEnabled={data?.isDokployNetworkInstalled}
|
isEnabled={data?.ssh.permitRootLogin === "no"}
|
||||||
description={
|
description={
|
||||||
data?.isDokployNetworkInstalled
|
data?.ssh.permitRootLogin === "no"
|
||||||
? "Created"
|
? "Disabled (Recommended)"
|
||||||
: "Not Created"
|
: `Enabled: ${data?.ssh.permitRootLogin} (Root Login should be disabled)`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Main Directory Created"
|
label="Use PAM"
|
||||||
isEnabled={data?.isMainDirectoryInstalled}
|
isEnabled={data?.ssh.usePam === "no"}
|
||||||
description={
|
description={
|
||||||
data?.isMainDirectoryInstalled
|
data?.ssh.usePam === "no"
|
||||||
? "Created"
|
? "Disabled (Recommended for key-based auth)"
|
||||||
: "Not Created"
|
: "Enabled (Should be disabled when using key-based auth)"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border rounded-lg p-4">
|
||||||
|
<h3 className="text-lg font-semibold mb-1">Fail2Ban</h3>
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
Fail2Ban (Fail2Ban) is a service that can be used to prevent
|
||||||
|
brute force attacks on your server.
|
||||||
|
</p>
|
||||||
|
<div className="grid gap-2.5">
|
||||||
|
<StatusRow
|
||||||
|
label="Installed"
|
||||||
|
isEnabled={data?.fail2ban.installed}
|
||||||
|
description={
|
||||||
|
data?.fail2ban.installed
|
||||||
|
? "Installed (Recommended)"
|
||||||
|
: "Not Installed (Fail2Ban should be installed for protection against brute force attacks)"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatusRow
|
||||||
|
label="Enabled"
|
||||||
|
isEnabled={data?.fail2ban.enabled}
|
||||||
|
description={
|
||||||
|
data?.fail2ban.enabled
|
||||||
|
? "Enabled (Recommended)"
|
||||||
|
: "Not Enabled (Fail2Ban service should be enabled)"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StatusRow
|
||||||
|
label="Active"
|
||||||
|
isEnabled={data?.fail2ban.active}
|
||||||
|
description={
|
||||||
|
data?.fail2ban.active
|
||||||
|
? "Active (Recommended)"
|
||||||
|
: "Not Active (Fail2Ban service should be running)"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatusRow
|
||||||
|
label="SSH Protection"
|
||||||
|
isEnabled={data?.fail2ban.sshEnabled === "true"}
|
||||||
|
description={
|
||||||
|
data?.fail2ban.sshEnabled === "true"
|
||||||
|
? "Enabled (Recommended)"
|
||||||
|
: "Not Enabled (SSH protection should be enabled to prevent brute force attacks)"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatusRow
|
||||||
|
label="SSH Mode"
|
||||||
|
isEnabled={data?.fail2ban.sshMode === "aggressive"}
|
||||||
|
description={
|
||||||
|
data?.fail2ban.sshMode === "aggressive"
|
||||||
|
? "Aggressive Mode (Recommended)"
|
||||||
|
: `Mode: ${data?.fail2ban.sshMode || "Not Set"} (Aggressive mode recommended for better protection)`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
getPublicIpWithFallback,
|
getPublicIpWithFallback,
|
||||||
haveActiveServices,
|
haveActiveServices,
|
||||||
removeDeploymentsByServerId,
|
removeDeploymentsByServerId,
|
||||||
|
serverAudit,
|
||||||
serverSecurity,
|
serverSecurity,
|
||||||
serverSetup,
|
serverSetup,
|
||||||
serverValidate,
|
serverValidate,
|
||||||
@@ -179,11 +180,36 @@ export const serverRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to validate this server",
|
message: "You are not authorized to validate this server",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const response = await serverSecurity(input.serverId);
|
const response = await serverAudit(input.serverId);
|
||||||
return {} as unknown as {
|
console.log(response);
|
||||||
docker: {
|
return response as unknown as {
|
||||||
|
ufw: {
|
||||||
|
installed: boolean;
|
||||||
|
active: boolean;
|
||||||
|
defaultIncoming: string;
|
||||||
|
};
|
||||||
|
ssh: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
version: string;
|
keyAuth: boolean;
|
||||||
|
permitRootLogin: string;
|
||||||
|
passwordAuth: string;
|
||||||
|
usePam: string;
|
||||||
|
};
|
||||||
|
nonRootUser: {
|
||||||
|
hasValidSudoUser: boolean;
|
||||||
|
};
|
||||||
|
unattendedUpgrades: {
|
||||||
|
installed: boolean;
|
||||||
|
active: boolean;
|
||||||
|
updateEnabled: number;
|
||||||
|
upgradeEnabled: number;
|
||||||
|
};
|
||||||
|
fail2ban: {
|
||||||
|
installed: boolean;
|
||||||
|
enabled: boolean;
|
||||||
|
active: boolean;
|
||||||
|
sshEnabled: string;
|
||||||
|
sshMode: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export * from "./setup/server-security";
|
|||||||
export * from "./setup/setup";
|
export * from "./setup/setup";
|
||||||
export * from "./setup/traefik-setup";
|
export * from "./setup/traefik-setup";
|
||||||
export * from "./setup/server-validate";
|
export * from "./setup/server-validate";
|
||||||
|
export * from "./setup/server-audit";
|
||||||
|
|
||||||
export * from "./utils/backups/index";
|
export * from "./utils/backups/index";
|
||||||
export * from "./utils/backups/mariadb";
|
export * from "./utils/backups/mariadb";
|
||||||
|
|||||||
@@ -26,18 +26,18 @@ const validateSsh = () => `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const validateNonRootUser = () => `
|
const validateNonRootUser = () => `
|
||||||
sudoUsers=$(grep -Po '^sudo:.*:\\K.*$' /etc/group | tr ',' '\\n' | grep -v root)
|
sudoUsers=\$(grep -Po '^sudo:.*:\\K.*$' /etc/group | tr ',' '\\n' | grep -v root)
|
||||||
adminUsers=$(grep -Po '^admin:.*:\\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 '^$')
|
privilegedUsers=\$(echo -e "\${sudoUsers}\\n\${adminUsers}" | sort -u | grep -v '^$')
|
||||||
validUserFound=false
|
validUserFound=false
|
||||||
|
|
||||||
while IFS= read -r user; do
|
while IFS= read -r user; do
|
||||||
userShell=$(getent passwd "$user" | cut -d: -f7)
|
userShell=\$(getent passwd "\$user" | cut -d: -f7)
|
||||||
if [[ "$userShell" != "/usr/sbin/nologin" && "$userShell" != "/bin/false" ]]; then
|
if [[ "\$userShell" != "/usr/sbin/nologin" && "\$userShell" != "/bin/false" ]]; then
|
||||||
validUserFound=true
|
validUserFound=true
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done <<< "$privilegedUsers"
|
done <<< "\$privilegedUsers"
|
||||||
|
|
||||||
echo "{\\"hasValidSudoUser\\": $validUserFound}"
|
echo "{\\"hasValidSudoUser\\": $validUserFound}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -89,6 +89,32 @@ export const serverSecurity = async (serverId: string) => {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_dependencies() {
|
||||||
|
echo -e "Checking required dependencies..."
|
||||||
|
|
||||||
|
local required_commands=("curl" "jq" "systemctl" "apt-get")
|
||||||
|
local missing_commands=()
|
||||||
|
|
||||||
|
for cmd in "\${required_commands[@]}"; do
|
||||||
|
if ! command -v "\$cmd" >/dev/null 2>&1; then
|
||||||
|
missing_commands+=("\$cmd")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ \${#missing_commands[@]} -ne 0 ]; then
|
||||||
|
echo -e "\${RED}The following required commands are missing:\${NC}"
|
||||||
|
for cmd in "\${missing_commands[@]}"; do
|
||||||
|
echo " - \$cmd"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo -e "\${YELLOW}Please install these commands before running this script.\${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "All required dependencies are installed\n"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
os=$(check_os)
|
os=$(check_os)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user