/
#!/usr/bin/bash
#
# This script add IMA signatures to installed RPM package files
usage() {
    echo "Add IMA signatures to installed packages."
    cat <<EOF
usage: $0 [--package=PACKAGE_NAME|ALL] [--ima-cert=IMA_CERT_PATH] [--reinstall_threshold=NUM]

       --package
       By default, it will add IMA sigantures to all installed package files.
       Or you can provide a package name to only add IMA signature for files of
       specicifed package.

       --reinstall_threshold
       When there are >reinstall_threshold (=20 by default) packages in the RPM
       DB missing IMA signatures, reinstalling the packages to add IMA
       signatures to the packages.  By default, IMA sigatures will be obtained
       from the RPM DB. However the RPM DB may not have the signatures. Dectect
       this case by checking if there are >reinstall_threshold package missing
       IMA signatures.

       --ima-cert
       With the signing IMA cert path specified, it will also try to verify the
       added IMA signature.

EOF
    exit 1
}

for _opt in "$@"; do
    case "$_opt" in
    --reinstall_threshold=*)
        reinstall_threshold=${_opt#*=}
        ;;
    --package=*)
        package=${_opt#*=}
        ;;
    --ima_cert=*)
        ima_cert=${_opt#*=}
        ;;
    *)
        [[ -n $1 ]] && usage
        ;;
    esac
done

if [[ -z $package ]] || [[ $package == ALL ]]; then
    package="--all"
fi

abort() {
    echo "$1"
    exit 1
}

# Add IMA signatures from RPM database
add_from_rpm_db() {
    if ! command -v setfattr &>/dev/null; then
        abort "Please install attr"
    fi

    # use "|" as deliminator since it won't be used in a filename or signature
    while IFS="|" read -r path sig; do
        # [[ -z "$sig" ]] somehow doesn't work for some files that don't have IMA
        # signatures. This may be a issue of rpm
        if [[ "$sig" != "0"* ]]; then
            continue
        fi

        # Skip directory, soft links, non-existent files and vfat fs
        if [[ -d "$path" || -L "$path" || ! -f "$path" || "$path" == "/boot/efi/EFI/"* ]]; then
            continue
        fi

        if ! setfattr -n security.ima "$path" -v "0x$sig"; then
            echo "Failed to add IMA sig for $path"
        fi

        [[ -e "$ima_cert" ]] || continue
        # TODO
        # don't verify the modified files like /etc?
        if ! evmctl ima_verify -k "$ima_cert" "$path" &>/dev/null; then
            echo "Failed to verify $path"
        fi
    done < <(rpm -q --queryformat "[%{FILENAMES}|%{FILESIGNATURES}\n]" "$package")
}

# Add IMA signatures by reinstalling all packages
add_by_reinstall() {
    [[ $package == "--all" ]] && package='*'
    dnf reinstall "$package" -yq >/dev/null
}

if [[ -z $reinstall_threshold ]]; then
    if [[ $package == "--all" ]]; then
        reinstall_threshold=20
    else
        if ! rpm -q --quiet "$package"; then
            dnf install "$package" -yq >/dev/null
            exit 0
        fi
        reinstall_threshold=1
    fi
fi

unsigned_packages_in_rpm_db=$(rpm -q --queryformat "%{SIGPGP:pgpsig}\n" "$package" | grep "^(none)$" | wc -l)

if [[ $unsigned_packages_in_rpm_db -ge $reinstall_threshold ]]; then
    add_by_reinstall
else
    add_from_rpm_db
fi