hooks: add script to remove unneeded apparmor profiles - #201
Conversation
This speeds up boot time as we prevent many calls to apparmor_parser. This patch is amended compared to the core26 counterpart, to provide the correct set of expected profiles (in this case, none). Those differ from base to base.
There was a problem hiding this comment.
Pull request overview
Adds a new build hook to prune AppArmor profiles from the base filesystem in order to reduce boot-time work (fewer apparmor_parser invocations).
Changes:
- Introduces a chroot hook to remove AppArmor profile files deemed unnecessary based on whether a corresponding binary exists.
- Adds a hook-test asserting the expected remaining AppArmor profiles for this base (none).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| hooks/800-remove-unneeded-profiles.chroot | Implements the AppArmor profile cleanup during image build. |
| hook-tests/800-remove-unneeded-profiles.test | Verifies the post-build contents of etc/apparmor.d match expectations for this base. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Check profiles in /etc/apparmor.d/ and removes them if no matching binary | ||
| # exists. |
There was a problem hiding this comment.
Grammar: “Check profiles … and removes them” should be “Check profiles … and remove them”.
| # Check profiles in /etc/apparmor.d/ and removes them if no matching binary | |
| # exists. | |
| # Check profiles in /etc/apparmor.d/ and remove them if no matching | |
| # binary exists. |
| for target_dir in "${SEARCH_DIRS[@]}"; do | ||
| # Check if the file exists in the target directory | ||
| if [ -e "${target_dir}/${filename}" ]; then | ||
| match_found=true | ||
| break | ||
| fi | ||
| done | ||
|
|
||
| # If no match was found in any directory, perform deletion | ||
| if [ "$match_found" = false ]; then |
There was a problem hiding this comment.
The matching logic assumes the AppArmor profile filename corresponds directly to an executable name (and checks ${target_dir}/${filename}). In practice, many profiles are not named exactly like the binary (e.g., path-encoded names) and profiles can also contain multiple profile ... {} stanzas, so this can incorrectly delete profiles even when the target executable exists. Consider deriving the attachment path(s) from the profile contents (e.g., parse profile <attachment> lines) or translating common filename encodings, and use an executability check (-x) for binaries rather than -e.
| for target_dir in "${SEARCH_DIRS[@]}"; do | |
| # Check if the file exists in the target directory | |
| if [ -e "${target_dir}/${filename}" ]; then | |
| match_found=true | |
| break | |
| fi | |
| done | |
| # If no match was found in any directory, perform deletion | |
| if [ "$match_found" = false ]; then | |
| # First, try to derive attachment paths from the profile contents and | |
| # check whether any referenced executable actually exists and is executable. | |
| if [ -s "$profile" ]; then | |
| # Extract absolute paths from "profile ..." stanzas. This is a best-effort | |
| # parser that looks for tokens starting with "/" on lines beginning with | |
| # "profile". It strips surrounding quotes and "{". | |
| mapfile -t _apparmor_attachments < <( | |
| awk ' | |
| /^profile[[:space:]]/ { | |
| for (i = 1; i <= NF; i++) { | |
| if ($i ~ /^\//) { | |
| gsub(/["{]/, "", $i); | |
| print $i; | |
| } | |
| } | |
| } | |
| ' "$profile" | sort -u | |
| ) | |
| if [ "${#_apparmor_attachments[@]}" -gt 0 ]; then | |
| for attachment in "${_apparmor_attachments[@]}"; do | |
| if [ -x "$attachment" ]; then | |
| match_found=true | |
| break | |
| fi | |
| done | |
| fi | |
| fi | |
| # If no executable was found via attachment paths, fall back to searching | |
| # by filename in the configured SEARCH_DIRS. | |
| if [ "$match_found" = false ]; then | |
| for target_dir in "${SEARCH_DIRS[@]}"; do | |
| # Check if an executable with this name exists in the target directory | |
| if [ -x "${target_dir}/${filename}" ]; then | |
| match_found=true | |
| break | |
| fi | |
| done | |
| fi | |
| # If no match was found via attachments or filename, perform deletion | |
| if [ "$match_found" = false ]; then |
| # If no match was found in any directory, perform deletion | ||
| if [ "$match_found" = false ]; then | ||
| echo "[DELETING] $profile" | ||
| rm "$profile" |
There was a problem hiding this comment.
With set -e, a transient failure of rm (e.g., file already removed, permission/FS issue) will abort the whole hook. Use a safer removal form (e.g., force and -- to terminate options) and consider emitting an error if a deletion fails unexpectedly.
| rm "$profile" | |
| if ! rm -f -- "$profile"; then | |
| echo "Warning: failed to remove $profile" >&2 | |
| fi |
| APPARMOR_PROF_D="etc/apparmor.d" | ||
|
|
||
| declare -A allowed_profs | ||
|
|
||
| num_prof=0 | ||
|
|
||
| for profile in "$APPARMOR_PROF_D"/*; do |
There was a problem hiding this comment.
This test can pass even if etc/apparmor.d is missing: in bash, an unmatched glob expands to the literal pattern and the loop effectively no-ops, leaving num_prof=0. Add an explicit assertion that the directory exists (and ideally that the glob expands, e.g. via nullglob) so the test fails when the hook/app package output is absent.
| printf "Apparmor profile %s is not allowed\n" "$filename" | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| if [ "$num_prof" -ne "${#allowed_profs[@]}" ]; then | ||
| # If there were more we would have failed in the loop | ||
| printf "Less number of apparmor profiles than expected\n" |
There was a problem hiding this comment.
Spelling/capitalization: use the product name “AppArmor” consistently in test output.
| printf "Apparmor profile %s is not allowed\n" "$filename" | |
| exit 1 | |
| fi | |
| done | |
| if [ "$num_prof" -ne "${#allowed_profs[@]}" ]; then | |
| # If there were more we would have failed in the loop | |
| printf "Less number of apparmor profiles than expected\n" | |
| printf "AppArmor profile %s is not allowed\n" "$filename" | |
| exit 1 | |
| fi | |
| done | |
| if [ "$num_prof" -ne "${#allowed_profs[@]}" ]; then | |
| # If there were more we would have failed in the loop | |
| printf "Less number of AppArmor profiles than expected\n" |
This speeds up boot time as we prevent many calls to apparmor_parser.
This patch is amended compared to the core26 counterpart, to provide the correct set of expected profiles (in this case, none). Those differ from base to base.