通过分析和删除不必要的软件包来强化 Ubuntu 22.04 的服务器

Tho*_*usz 8 server package-management security bash dependencies

我读过 Jay Lacroix 的关于“Mastering Ubuntu Server”的书,他建议删除所有不必要的软件包以减少攻击面。具体来说,他建议运行apt-cache rdepends <package>以查明是否还有其他软件包取决于我们考虑删除的软件包。

我编写了一个 bash 脚本,列出了所有已安装包的依赖包,但需要很长时间(在 Raspberry Pi 4、8GB 上超过 30 分钟),我想知道是否有更好、更快的解决方案。

#!/bin/bash

readarray -t packages < <(dpkg --get-selections | cut -f1)

for package in ${packages[@]};
do
    readarray -t dependents < <(apt-cache rdepends $package | sed -n '3,$s/^\s*//p')
    echo "-----------------------------------------------------------------------" | tee -a packages_and_depents.txt
    echo "${package} has these dependents on the system of max ${#dependents[@]}:" | tee -a packages_and_depents.txt
    echo "-----------------------------------------------------------------------" | tee -a packages_and_depents.txt
    for dependent in ${dependents[@]};
    do
        dpkg --get-selections $dependent 2>/dev/null | tee -a packages_and_depents.txt
    done
done
Run Code Online (Sandbox Code Playgroud)

Art*_*ild 12

dpkg系统为每个包都有一个字段,指示其Priority

您可以使用它作为初始过滤器,并且仅在分类为optional和的包上运行脚本extra(并省略required,importantstandard)。

此外,创建一个额外的数组并for为每个包运行一个额外的循环似乎没有必要,并且肯定会占用更多的计算能力。

所以我删除了第二个for循环,而是--installed直接添加到apt-cache rdepends命令中。

这可以通过修改脚本来完成,如下所示:

#!/bin/bash

# The command for this line is changed
readarray -t packages < <(dpkg-query -Wf '${Package}${Status}${Priority}\n' | sort -b -k5,5 -k1,1 | grep -v 'required\|important\|standard' | grep 'installed' | awk '{ print $1 }')

for package in ${packages[@]};
do
    echo "--------------------------------------------------------" | tee -a packages_and_depents.txt
    echo "${package} has these dependents installed on the system:" | tee -a packages_and_depents.txt
    echo "--------------------------------------------------------" | tee -a packages_and_depents.txt

    # 2nd for loop removed and replaced with `--installed` option
    apt-cache --installed rdepends "$package" | tail -n +3 | tee -a packages_and_depents.txt
done
Run Code Online (Sandbox Code Playgroud)

另一种选择是更改整个脚本,因此它不显示所有反向依赖项,而是仅显示那些没有反向依赖项的包的名称(那些是要删除的候选包)。

grep另外,我认为您可以通过排除名称以lib(添加)开头的所有包来添加额外的排除grep -v '^lib'

最后,可以改进演示文稿,因此脚本在运行时会提供视觉反馈,但最终报告仅写入输出文件。

这是我的脚本的最终版本:

#!/bin/bash

# The command for this line is changed
readarray -t packages < <(dpkg-query -Wf '${Package}${Status}${Priority}\n' | sort -b -k5,5 -k1,1 | grep -v 'required\|important\|standard' | grep -v '^lib' | grep 'installed' | awk '{ print $1 }')

# Write to file
echo "The following packages are not a dependency to any installed package:" > packages_no_depends.txt
# Write to screen
echo "Number of packages: [ ${#packages[@]} ] (priority optional/extra)"
echo ""

i=0
j=0

# Loop that only prints package names with NO reverse dependencies
for package in ${packages[@]};
do

    (( j++ ))
    echo -e "\033[1AProcessed packages: [ $i/$j ]"
    if [[ $(apt-cache --installed rdepends "$package" | tail -n +3 | wc -l) -eq 0 ]]
    then
        # Write to file
        echo "  $package" >> packages_no_depends.txt
        # Write to screen
        echo -e "\033[K  Package $package added to the list of non-dependencies\033[1A"
        (( i++ ))
    fi

done

# Final overview
echo -e "\033[K"
echo "STATUS"
echo "======"
echo "  Total packages scanned : $j"
echo "  Candidates for removal : $i"
echo "  Script execution time  : $SECONDS seconds"
Run Code Online (Sandbox Code Playgroud)

参考光标移动的转义码

编辑:此解决方案主要应考虑布局和演示 - Raffa 的解决方案更有效,因此您可以根据喜好自行组合两者。

根据 Raffa 的意见,这是我的脚本的最终版本:

#!/bin/bash

# Change /path/to
dpkg_file="/path/to/packages_no_depends.txt"

# Write to file
echo "The following packages are not a dependency to any installed package:" > "$dpkg_file"
# Write to screen
echo "Scanning packages ..."

# Function to write non-dependencies to file
dpkg-query -Wf '${Package} ${Status}${Priority}\n' |
  grep -v 'required\|important\|standard' |
  grep 'installed' |
  awk '{ print $1 }' |
xargs apt-cache rdepends --installed |
  awk '! /Reverse Depends:/ {
    tp = $0
    n++
  }
  /Reverse Depends:/ {
    if (n == 1 && NR != 2) {
        print "  " p
    }
    n = 0
    p = tp
  }
  END {
    if (n == 0) {
        print "  " p
    }
  }' >> "$dpkg_file"

# Final overview
echo -e "\nSTATUS\n======"
echo "  Total packages scanned : $(dpkg-query -Wf '${Package}${Status}${Priority}\n' | grep -v 'required\|important\|standard' | grep 'installed' | wc -l)"
echo "  Candidates for removal : $(tail -n +2 $dpkg_file | wc -l)"
echo "  Script execution time  : $SECONDS seconds"
Run Code Online (Sandbox Code Playgroud)

  • 较新的 APT 版本可以采用[搜索模式](https://manpages.ubuntu.com/manpages/jammy/man7/apt-patterns.7.html)`?reverse-depends(PATERN)`,可以通过前置来否定前面的`!`就像`!?reverse-depends(PATERN)`…但是,我不知道它是否适用于这个用例。 (2认同)
  • @artur-meinild 谢谢!这两个脚本都工作得很好并且大大减少了执行时间。我坚持使用你的第二个脚本,这正是我正在寻找的。我将第二个脚本中的第 9 行简化为:`echo "Number of packages: [ ${#packages[@]} ] (priority optional/extra)"` (2认同)
  • FWIW,如果包名称超过 40 个字符,脚本会抱怨名称奇怪的包不存在。可以通过删除包名称限制并强制使用以下格式的空格字符来修复此问题:`'${Package} ${Status;-26}${Priority}\n'`。例如,对我来说,这个包导致了奇怪的输出:“network-manager-config-connectivity-ubuntu” (2认同)

Raf*_*ffa 8

根据您在脚本中实现的工具,这应该尽可能快:

dpkg --get-selections |
cut -f1 |
xargs apt-cache rdepends --installed |
awk '! /Reverse Depends:/ {
    tp = $0
    n++
}

/Reverse Depends:/ {
    if (n == 1 && NR != 2) {
        print p
    }
    n = 0
    p = tp
}

END {
    if (n == 0) {
        print p
    }
}
'
Run Code Online (Sandbox Code Playgroud)

它应该在输出中列出dpkg --get-selections | cut -f1未安装的软件包,以反转对系统的依赖关系。

!!警告!!

切勿将上述命令的输出提供给软件包删除工具...检查输出并在所有情况下手动处理它。

  • 你的实施非常棒。我的 Raspberry Pi 4, 8GB 上的运行时间约为 1 秒!我会将您的脚本与 Artur Meinild 的部分脚本结合起来作为最终的实现。很酷的团队努力。 (2认同)
  • 看起来真的很酷。我可以看到我需要更多地研究“xargs”等。 (2认同)
  • +1 明确警告深思熟虑的人工审查至关重要。 (2认同)