feat: add security audit

This commit is contained in:
Mauricio Siu
2024-12-15 21:13:37 -06:00
parent c0acdc5df1
commit 58c2ceb355
5 changed files with 182 additions and 52 deletions

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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}"
`; `;

View File

@@ -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)