Skip to content

Commit 1ff1589

Browse files
committed
feat: add cryptpilot-enhance Secure VM disk image hardening tool
This commit introduces `cryptpilot-enhance.sh`, a new utility that securely hardens offline VM disk images (e.g., QCOW2) before encryption. The script uses `virt-customize` to apply system-level security configurations in a single guest launch, minimizing performance overhead and ensuring compatibility with secure or containerized build environments. Key features: - Supports two hardening modes: 'full' (maximum security, removes SSH) and 'partial' (retains SSH with key-only access) - Removes cloud-specific agents (Aliyun Cloud Assistant, Aegis/Security Center) and unneeded services (rpcbind, cloud-init) - Locks root/admin passwords and cleans up non-essential user accounts and sensitive data (e.g., .DEL dirs, shell history) - Hardens SSH configuration in partial mode (disables password login, X11/TCP forwarding) - Optionally injects SSH public keys for authorized root login - Includes support for direct libguestfs backend (LIBGUESTFS_BACKEND=direct), ideal for CI/containers The following files are added: - `cryptpilot-enhance.sh`: Main executable script with comprehensive logging and error handling - `docs/cryptpilot_enhance.md`: Detailed documentation in English - `docs/cryptpilot_enhance_zh.md`: Corresponding documentation in Chinese - Updated `cryptpilot.spec` to include the new binary in package installation The tool is designed for use in secure pipelines where minimal attack surface and audit compliance are required. Users are advised to test on image copies due to irreversible modifications. Signed-off-by: Kun Lai <laikun@linux.alibaba.com>
1 parent c749d65 commit 1ff1589

File tree

4 files changed

+554
-0
lines changed

4 files changed

+554
-0
lines changed

cryptpilot-enhance.sh

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
# Ensure consistent locale for parsing.
6+
export LC_ALL=C
7+
8+
# ANSI color codes
9+
readonly RED='\033[0;31m'
10+
readonly GREEN='\033[0;32m'
11+
readonly YELLOW='\033[1;33m'
12+
readonly PURPLE='\033[0;35m'
13+
readonly CYAN='\033[0;36m'
14+
readonly NC='\033[0m' # No Color
15+
16+
# Colored logging functions
17+
log::info() {
18+
# https://stackoverflow.com/a/7287873/15011229
19+
#
20+
# note: printf is used instead of echo to avoid backslash
21+
# processing and to properly handle values that begin with a '-'.
22+
printf "${CYAN}ℹ️ %s${NC}\n" "$*" >&2
23+
}
24+
25+
log::success() {
26+
printf "${GREEN}✅ %s${NC}\n" "$*" >&2
27+
}
28+
29+
log::warn() {
30+
printf "${YELLOW}⚠️ %s${NC}\n" "$*" >&2
31+
}
32+
33+
log::error() {
34+
printf "${RED}❌ ERROR: %s${NC}\n" "$*" >&2
35+
}
36+
37+
log::highlight() {
38+
printf "${PURPLE}📌 %s${NC}\n" "$*" >&2
39+
}
40+
41+
proc::fatal() {
42+
log::error "$@"
43+
exit 1
44+
}
45+
46+
usage() {
47+
cat <<'EOF'
48+
Usage: cryptpilot-enhance [OPTIONS]
49+
50+
Securely harden a VM disk image before encryption using virt-customize.
51+
All operations are performed in a single guest launch for optimal performance.
52+
53+
OPTIONS:
54+
--mode MODE Hardening level: 'full' or 'partial'
55+
- full: Maximum security (removes SSH, locks all passwords)
56+
- partial: Retains SSH with key-only access, milder user restrictions
57+
--image IMAGE_PATH Path to the disk image (qcow2 or raw format)
58+
--ssh-key KEY_FILE (Optional) Public SSH key file to inject for root login (partial mode only)
59+
--help Show this help message and exit
60+
61+
NOTES:
62+
- Requires libguestfs-tools (virt-customize). Install via:
63+
CentOS/RHEL: yum install -y libguestfs-tools
64+
Ubuntu: apt-get install -y libguestfs-tools
65+
- By default, virt-customize uses the 'libvirt' backend (via libvirtd). For better performance
66+
or to avoid daemon dependencies, set the backend explicitly using LIBGUESTFS_BACKEND:
67+
- To use direct QEMU without libvirt (recommended for CI/containers):
68+
export LIBGUESTFS_BACKEND=direct
69+
- Example:
70+
LIBGUESTFS_BACKEND=direct cryptpilot-enhance --mode partial --image ./disk.qcow2
71+
- The 'direct' backend avoids libvirtd overhead and works in containerized or minimal environments.
72+
- Always test on a copy of the image before production use.
73+
EOF
74+
}
75+
76+
# Parse arguments
77+
MODE="" IMAGE="" SSH_KEY=""
78+
79+
while [[ "$#" -gt 0 ]]; do
80+
case $1 in
81+
--mode)
82+
MODE="$2"
83+
shift 2
84+
;;
85+
--image)
86+
IMAGE="$2"
87+
shift 2
88+
;;
89+
--ssh-key)
90+
SSH_KEY="$2"
91+
shift 2
92+
;;
93+
-h | --help)
94+
usage
95+
exit 0
96+
;;
97+
*)
98+
log::error "unknown argument $1"
99+
usage
100+
exit 1
101+
;;
102+
esac
103+
done
104+
105+
# Validate inputs
106+
[[ -z "$MODE" ]] && {
107+
log::error "--mode is required."
108+
usage
109+
exit 1
110+
}
111+
[[ ! "$MODE" =~ ^(full|partial)$ ]] && {
112+
log::error "--mode must be 'full' or 'partial'."
113+
exit 1
114+
}
115+
[[ -z "$IMAGE" ]] && {
116+
log::error "--image is required."
117+
usage
118+
exit 1
119+
}
120+
[[ ! -f "$IMAGE" ]] && {
121+
log::error "image file not found: $IMAGE"
122+
exit 1
123+
}
124+
[[ "$MODE" == "partial" && -n "$SSH_KEY" && ! -f "$SSH_KEY" ]] && {
125+
log::error "SSH key file not found: $SSH_KEY"
126+
exit 1
127+
}
128+
129+
log::info "Starting image hardening..."
130+
log::highlight "Image: $IMAGE"
131+
log::highlight "Mode: $MODE"
132+
133+
# Build virt-customize command (single execution)
134+
VIRT_CMD=(
135+
virt-customize
136+
--format=qcow2
137+
-a "$IMAGE"
138+
)
139+
140+
# Helper function to append --run-command
141+
add_run_cmd() {
142+
VIRT_CMD+=(--run-command "$1")
143+
}
144+
145+
# =============================
146+
# 1. Uninstall Cloud Assistant Agent (Aliyun Assist)
147+
# =============================
148+
add_run_cmd '
149+
# Stop Cloud Assistant daemon
150+
/usr/local/share/assist-daemon/assist_daemon --stop
151+
152+
# Stop Cloud Assistant service
153+
systemctl stop aliyun.service
154+
155+
# Remove Cloud Assistant daemon
156+
/usr/local/share/assist-daemon/assist_daemon --delete
157+
158+
# Uninstall package
159+
rpm -qa | grep aliyun_assist | xargs rpm -e
160+
161+
# Clean up leftover files and service configurations
162+
rm -rf /usr/local/share/aliyun-assist
163+
rm -rf /usr/local/share/assist-daemon
164+
rm -f /etc/systemd/system/aliyun.service
165+
rm -f /etc/init.d/aliyun-service
166+
'
167+
168+
# =============================
169+
# 2. Uninstall Cloud Security Center (Aegis/AntiKnight)
170+
# =============================
171+
add_run_cmd '
172+
# Download and execute uninstall script
173+
wget "http://update.aegis.aliyun.com/download/uninstall.sh" && chmod +x uninstall.sh && ./uninstall.sh
174+
'
175+
176+
# =============================
177+
# 3. Disable and Mask rpcbind Service
178+
# =============================
179+
add_run_cmd '
180+
systemctl stop rpcbind.service
181+
systemctl disable rpcbind.service
182+
systemctl mask rpcbind.service
183+
184+
systemctl stop rpcbind.socket
185+
systemctl disable rpcbind.socket
186+
systemctl mask rpcbind.socket
187+
'
188+
189+
# =============================
190+
# 4. Remove cloud-init
191+
# =============================
192+
add_run_cmd '
193+
yum remove -y cloud-init
194+
'
195+
196+
# =============================
197+
# 5. Optional: Restrict or Remove SSH Service
198+
# =============================
199+
if [[ "$MODE" == "full" ]]; then
200+
add_run_cmd '
201+
yum remove -y openssh-server
202+
'
203+
elif [[ "$MODE" == "partial" ]]; then
204+
add_run_cmd '
205+
# Disable password login, allow public key only
206+
sed -i "s/^#*PasswordAuthentication.*/PasswordAuthentication no/" /etc/ssh/sshd_config
207+
sed -i "s/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/" /etc/ssh/sshd_config
208+
209+
# Prevent root password login, but allow key-based login
210+
sed -i "s/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/" /etc/ssh/sshd_config
211+
212+
# Disable high-risk features
213+
sed -i "s/^#*X11Forwarding.*/X11Forwarding no/" /etc/ssh/sshd_config
214+
sed -i "s/^#*AllowTcpForwarding.*/AllowTcpForwarding no/" /etc/ssh/sshd_config
215+
'
216+
fi
217+
218+
# =============================
219+
# 6. Disable Linux User Password Login (Prevent Console Access)
220+
# =============================
221+
# shellcheck disable=SC2016
222+
add_run_cmd '
223+
#!/bin/bash
224+
225+
# 1. Lock root and admin account passwords:
226+
echo "1. Locking root and admin account passwords..."
227+
sed -i "s/\(^root:\)[^:]*/\1!!/" /etc/shadow
228+
sed -i "s/\(^admin:\)[^:]*/\1!!/" /etc/shadow
229+
echo "1. Root and admin account passwords locked!"
230+
231+
echo "2. Processing other user accounts (excluding root and admin)..."
232+
# Define list of excluded usernames
233+
exclude_users=("root" "admin")
234+
235+
while IFS=: read -r username _ _ _ _ homedir user_shell; do
236+
# Check if shell is one of the interactive shells
237+
case $user_shell in
238+
"/bin/bash"|"/bin/sh"|"/bin/zsh" \
239+
|"/usr/bin/bash"|"/usr/bin/sh"|"/usr/bin/zsh" \
240+
|"/usr/local/bin/bash"|"/usr/local/bin/sh"|"/usr/local/bin/zsh")
241+
242+
# Skip if username is in exclude list
243+
if [[ " ${exclude_users[@]} " =~ " $username " ]]; then
244+
continue
245+
fi
246+
247+
# Read password field from shadow
248+
pass=$(grep "^$username:" /etc/shadow | cut -d: -f2)
249+
250+
# If already locked (!, !!, *, etc.), skip deletion
251+
if [[ "$pass" == "!" || "$pass" == "!!" || "$pass" == *\!* || "$pass" == "*" ]]; then
252+
echo "Account $username is already locked or disabled, skipping."
253+
continue
254+
fi
255+
256+
# Delete eligible user account and home directory
257+
echo "Removing account: $username"
258+
userdel -r "$username" 2>/dev/null || true
259+
if [[ -d "$homedir" ]]; then
260+
rm -rf "$homedir" 2>/dev/null || true
261+
fi
262+
;;
263+
264+
*)
265+
# Ignore users with non-interactive shells
266+
continue
267+
;;
268+
esac
269+
done < /etc/passwd
270+
echo "2. Other accounts (excluding root and admin) processed!"
271+
272+
echo "3. Removing directories ending with .DEL in home folders..."
273+
# Scan /etc/passwd again and clean *.DEL directories in valid home paths
274+
while IFS=: read -r username _ _ _ _ homedir user_shell; do
275+
case $user_shell in
276+
"/bin/bash"|"/bin/sh"|"/bin/zsh" \
277+
|"/usr/bin/bash"|"/usr/bin/sh"|"/usr/bin/zsh" \
278+
|"/usr/local/bin/bash"|"/usr/local/bin/sh"|"/usr/local/bin/zsh")
279+
if [[ -d "$homedir" ]]; then
280+
find "$homedir" -maxdepth 1 -type d -name "*.DEL" -exec rm -rf {} \;
281+
fi
282+
;;
283+
esac
284+
done < /etc/passwd
285+
echo "3. Cleanup of *.DEL directories completed!"
286+
'
287+
288+
# =============================
289+
# 7. Clear Bash History
290+
# =============================
291+
add_run_cmd '
292+
history -c && history -w
293+
'
294+
295+
# =============================
296+
# 8. (Partial Only) Inject SSH Public Key
297+
# =============================
298+
if [[ "$MODE" == "partial" && -n "$SSH_KEY" ]]; then
299+
log::info "Injecting SSH public key into root account..."
300+
VIRT_CMD+=(--ssh-inject "root:file:$SSH_KEY")
301+
fi
302+
303+
# =============================
304+
# Execute All Commands (Single Guest Boot)
305+
# =============================
306+
log::highlight "Using virt-customize in single-launch mode for efficiency"
307+
"${VIRT_CMD[@]}"
308+
309+
log::success "Hardening completed successfully!"

cryptpilot.spec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pushd src/
7070
install -d -p %{buildroot}%{_prefix}/bin
7171
install -p -m 755 %{_builddir}/%{name}-%{version}/install/cryptpilot/bin/cryptpilot %{buildroot}%{_prefix}/bin/cryptpilot
7272
install -p -m 755 cryptpilot-convert.sh %{buildroot}%{_prefix}/bin/cryptpilot-convert
73+
install -p -m 755 cryptpilot-enhance.sh %{buildroot}%{_prefix}/bin/cryptpilot-enhance
7374
# Install remain stuffs
7475
rm -rf %{buildroot}%{dracut_dst}
7576
install -d -p %{buildroot}%{dracut_dst}
@@ -118,6 +119,7 @@ rm -rf %{buildroot}
118119
%license src/LICENSE
119120
%{_prefix}/bin/cryptpilot
120121
%{_prefix}/bin/cryptpilot-convert
122+
%{_prefix}/bin/cryptpilot-enhance
121123
%{_prefix}/lib/systemd/system/cryptpilot.service
122124
%dir /etc/cryptpilot
123125
/etc/cryptpilot/global.toml.template

0 commit comments

Comments
 (0)