Useful CLI tools (bash) for Arch Linux administration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

371 lines
12 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. #!/usr/bin/env bash
  2. #
  3. # archrisks - Get security risk severity & count of installed packages on Arch Linux
  4. # Copyright (C) 2021 Pekka Helenius
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. #####################################
  19. red=$'\33[91m'
  20. orange=$'\033[38;5;208m'
  21. yellow=$'\033[93m'
  22. green=$'\033[92m'
  23. reset=$'\033[0m'
  24. typeset -A ARCH_MANAGERS
  25. # List of known Arch Linux package managers + priority number for each
  26. # Add more if you wish.
  27. # Syntax guidelines:
  28. # [<package manager priority (greater is more prioritized)>,<package manager>]="
  29. # <parameter to refresh local pkg repositories>|
  30. # <parameter to get detailed local pkg info>|
  31. # <parameter to get name and version string of a pkg>|
  32. # <parameter to get detailed remote pkg info>|
  33. # <root=requires root, no_root=does not require root>
  34. # "
  35. ARCH_MANAGERS=(
  36. [0,/usr/bin/pacman]="-Syy|-Qi|-Q|-Si|root"
  37. [1,/usr/bin/pacaur]="-Syy|-Qi|-Q|-Si|no_root"
  38. # [2,yaourt]="-Syy|-Qi|-Q|-Si|no_root"
  39. # [3,pikaur]="-Syy|-Qi|-Q|-Si|no_root"
  40. # [4,pacget]="-Syy|-Qi|-Q|-Si|no_root"
  41. # [5,yay]="-Syy|-Qi|-Q|-Si|no_root"
  42. # [6,foxaur]="-Syy|-Qi|-Q|-Si|no_root"
  43. # [7,aurum]="-Syy|-Qi|-Q|-Si|no_root"
  44. # [8,goaur]="-Syy|-Qi|-Q|-Si|no_root"
  45. # [9,aurs]="-Syy|-Qi|-Q|-Si|no_root"
  46. # [10,magico]="-Syy|-Qi|-Q|-Si|no_root"
  47. # [11,maur]="-Syy|-Qi|-Q|-Si|no_root"
  48. # [12,pkgbuilder]="-Syy|-Qi|-Q|-Si|no_root"
  49. # [13,spinach]="-Syy|-Qi|-Q|-Si|no_root"
  50. # [14,trizen]="-Syy|-Qi|-Q|-Si|no_root"
  51. )
  52. priority_lowlimit=-1
  53. default_order="level"
  54. default_reverse=0
  55. provider="security.archlinux.org"
  56. input_count=${#@}
  57. [[ $input_count -eq 1 ]] && input_1=${1}
  58. [[ $input_count -eq 2 ]] && input_1=${1}; input_2=${2}
  59. function helpCaller() {
  60. echo -e "
  61. Usage: $0
  62. -h|--help
  63. 1st arg: --sort=<name,issues,level,status,desc> (optional)
  64. 2nd arg: --reverse (optional)
  65. "
  66. exit 0
  67. }
  68. function inputParser() {
  69. if [[ ${input_count} -gt 2 ]] || [[ ${input_1} == "-h" ]] || [[ ${input_1} == "--help" ]]; then
  70. helpCaller
  71. elif [[ ${input_count} -eq 0 ]]; then
  72. sort_order=${default_order}
  73. sort_reverse=${default_reverse}
  74. else
  75. sort_order=$(echo ${input_1} | sed -r 's/^\-\-sort=(.*)/\1/')
  76. case ${sort_order} in
  77. name|issues|level|version|desc)
  78. echo "Custom sort order selected: ${sort_order}"
  79. ;;
  80. *)
  81. echo "Unknown sorting order selected (${sort_order})."
  82. helpCaller
  83. esac
  84. if [[ ${input_count} -eq 2 ]]; then
  85. case ${input_2} in
  86. "--reverse")
  87. echo "Reverse ordering"
  88. sort_reverse=1
  89. ;;
  90. *)
  91. echo "Unknown option '${input_2}'"
  92. sort_reverse=${default_reverse}
  93. esac
  94. fi
  95. fi
  96. }
  97. function internetTest() {
  98. if [[ $(ping -c 1 $provider 2>&1 | grep -c "Name or service not known") -ne 0 ]]; then
  99. echo -e "\nCan't connect to $provider. Please check your internet connection and try again.\n"
  100. exit 0
  101. fi
  102. }
  103. function findMyPackageManager() {
  104. i=0
  105. for managerStr in ${!ARCH_MANAGERS[@]}; do
  106. manager_priority=$(echo ${managerStr} | awk -F ',' '{print $1}')
  107. manager=$(echo ${managerStr} | awk -F ',' '{print $2}')
  108. if [[ ${manager_priority} -lt ${priority_lowlimit} ]]; then
  109. echo "Minimum priority is $((${priority_lowlimit} + 1)). You have a package which has lower priority value. Exiting."
  110. exit 1
  111. fi
  112. if [[ $(echo $(which ${manager} &>/dev/null)$?) -eq 0 ]]; then
  113. managers_list[$i]=${manager}
  114. managers_priority_list[$i]=${manager_priority}
  115. let i++
  116. fi
  117. done
  118. if [[ ${#managers_list[@]} -eq 0 ]]; then
  119. echo "Not any valid package manager found. Exiting."
  120. exit 1
  121. fi
  122. if [[ $(echo ${managers_priority_list[@]} | tr ' ' '\n' | uniq -d | wc -l) -ne 0 ]]; then
  123. echo "Package managers with same priority found. Check internal manager list for duplicates. Exiting."
  124. exit 1
  125. fi
  126. # Select package manager by priority. Highest is selected.
  127. i=0
  128. while [[ ${i} -le $((${#managers_list[@]} - 1)) ]]; do
  129. if [[ ${managers_priority_list[i]} -gt ${priority_lowlimit} ]]; then
  130. priority_lowlimit=${managers_priority_list[i]}
  131. selected_manager=${managers_list[i]}
  132. fi
  133. let i++
  134. done
  135. pkg_command=${ARCH_MANAGERS["$priority_lowlimit,$selected_manager"]}
  136. command_refresh=$(echo $pkg_command | awk -F '|' '{print $1}')
  137. command_pkginfo_local=$(echo $pkg_command | awk -F '|' '{print $2}')
  138. command_pkginfo_local_short=$(echo $pkg_command | awk -F '|' '{print $3}')
  139. command_pkginfo_remote=$(echo $pkg_command | awk -F '|' '{print $4}')
  140. command_require_root=$(echo $pkg_command | awk -F '|' '{print $5}')
  141. if [[ ${command_require_root} == "root" ]]; then
  142. if [[ ! $(id -u) -eq 0 ]]; then
  143. echo -e "\nThis command requires root privileges.\n"
  144. exit 0
  145. fi
  146. fi
  147. }
  148. function runTool() {
  149. echo "Security report date: $(date '+%d-%m-%Y, %X') (TZ: $(timedatectl status | grep "Time zone:" | awk '{print $3}'))"
  150. echo -e "\nSynchronizing package databases with ${selected_manager}\n"
  151. ${selected_manager} ${command_refresh} || exit
  152. if [[ ! $(which arch-audit | wc -l) -eq 1 ]]; then
  153. echo -e "\nCouldn't find Arch Linux security utility (arch-audit) in \$PATH. Please make sure it's installed.\n"
  154. else
  155. count=0
  156. prs_count=0
  157. IFS=$'\n'
  158. for i in $(arch-audit); do
  159. package_name=$(echo "$i" | awk -F ' ' '{print $1}')
  160. risk_level=$(echo "$i" | grep -oE "Low|Medium|High|Critical")
  161. risks_count=$(echo "$i" | grep -oP "(?<=by ).+(?=\. )" | sed 's/, /\n/g' | wc -l)
  162. #risks_count=$(echo "$i" | awk -F 'CVE' '{print NF-1}')
  163. risks[$count]="$package_name $risk_level $risks_count"
  164. let count++
  165. done
  166. echo -e "\nAnalyzing ${#risks[*]} vulnerable packages. This takes a while...\n"
  167. i=1
  168. for risk_parsed in $(echo "${risks[*]}"); do
  169. echo -en "Analysing package ${i}/${#risks[*]}... \r"
  170. # Package in question
  171. col1=$(echo "$risk_parsed" | awk -F ' ' '{print $1}')
  172. # Security issues detected
  173. col2=$(echo "$risk_parsed" | awk -F ' ' '{print $3}')
  174. #Critical, High, Medium or Low risk
  175. col3=$(echo "$risk_parsed" | awk -F ' ' '{print $2}' | sed 's/Critical/0/g; s/High/1/g; s/Medium/2/g; s/Low/3/g')
  176. col5=$(${selected_manager} ${command_pkginfo_local} $col1 | grep -i description | awk -F ": " '{print $2}')
  177. maxchars=35
  178. if [[ $(echo $col5 | wc -m) -gt $maxchars ]]; then
  179. col5=$(echo "$(echo $col5 | cut -c 1-$maxchars)...")
  180. fi
  181. versioncheck() {
  182. # TODO: We can't really depend on parsing output strings since they vary between Arch package managers
  183. parsedver() {
  184. echo $1 | awk -F ' ' '{print $2}' | sed -r 's/[a-z]+.*//; s/[:_+-]/\./g; s/[^0-9]$//;'
  185. }
  186. # Expected output syntax: "^<string> <version number>$"
  187. # TODO: We can't really depend on parsing output strings since they vary between Arch package managers
  188. system_version=$(${selected_manager} ${command_pkginfo_local_short} $1)
  189. repo_version=$(${selected_manager} ${command_pkginfo_remote} $1 | grep -E "^Version\s*:" | sed -r 's/.*(:\s*.*$)/\1/')
  190. version_array_1=$(parsedver $system_version)
  191. version_array_2=$(parsedver $repo_version)
  192. #Count of version elements (0 18 2 1 contains 4 numbers, for example)
  193. firstvernums=$(echo $version_array_1 | awk -F '.' '{print split($0, a)}')
  194. lastvernums=$(echo $version_array_2 | awk -F '.' '{print split($0, a)}')
  195. # Count of comparable version elements (maximum)
  196. # We compare this much of elements, not more
  197. if [[ $lastvernums -lt $firstvernums ]]; then
  198. comparables=$lastvernums
  199. else
  200. comparables=$firstvernums
  201. fi
  202. # If all numbers are same, we don't analyze them more deeply.
  203. if [[ $version_array_1 == $version_array_2 ]]; then
  204. col4="${green}Package is updated"
  205. else
  206. s=1
  207. while [ $s -le $comparables ]; do
  208. check1=$(echo -e $version_array_1 | awk -v var=$s -F '.' '{print $var}')
  209. check2=$(echo -e $version_array_2 | awk -v var=$s -F '.' '{print $var}')
  210. if [[ $check2 -gt $check1 ]]; then
  211. # Repo number is greater
  212. col4="${yellow}Update available"
  213. break
  214. elif [[ $check2 -lt $check1 ]]; then
  215. # System number is greater
  216. col4="${reset}Newer package installed"
  217. break
  218. fi
  219. let s++
  220. done
  221. fi
  222. }
  223. versioncheck $col1
  224. risk_entries[$prs_count]=$(printf "%s|%s|%s|%s|%s\n" "$col1" "$col2" "$col3" "$col4" "$col5")
  225. let prs_count++
  226. let i++
  227. done
  228. echo -e "\e[1m"
  229. printf "\n%-25s%-20s%-15s%-25s%s\n" "Package" "Security issues" "Risk level" "Version status" "Description"
  230. echo -e "\e[0m"
  231. sort_params=()
  232. case ${sort_order} in
  233. name)
  234. sort_column="-k1"
  235. ;;
  236. issues)
  237. sort_column="-k2"
  238. sort_params+=("-n")
  239. ;;
  240. level)
  241. sort_column="-k3"
  242. ;;
  243. version)
  244. sort_column="-k4"
  245. ;;
  246. desc)
  247. sort_column="-k5"
  248. ;;
  249. #*)
  250. # echo "Unknown sorting order selected. Exiting."
  251. # exit 1
  252. esac
  253. if [[ ${sort_reverse} == 1 ]]; then
  254. sort_params+=("-r")
  255. fi
  256. i=0
  257. for line in $(echo "${risk_entries[*]}" | sort ${sort_params[*]} -t'|' ${sort_column}); do
  258. if [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 0 ]]; then
  259. alert_color="${red}"
  260. importance="Critical"
  261. elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 1 ]]; then
  262. alert_color="${orange}"
  263. importance="High"
  264. elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 2 ]]; then
  265. alert_color="${yellow}"
  266. importance="Medium"
  267. elif [[ $(echo "$line" | awk -F '|' '{print $3}') -eq 3 ]]; then
  268. alert_color="${green}"
  269. importance="Low"
  270. fi
  271. sec_count=$(echo "$line" | awk -F '|' '{print $2}')
  272. if [[ $sec_count -lt 5 ]]; then
  273. secclr="${green}"
  274. elif [[ $sec_count -ge 5 ]] && [[ $sec_count -lt 10 ]]; then
  275. secclr="${yellow}"
  276. elif [[ $sec_count -ge 10 ]] && [[ $sec_count -lt 20 ]]; then
  277. secclr="${orange}"
  278. elif [[ $sec_count -ge 20 ]]; then
  279. secclr="${red}"
  280. fi
  281. secsum[$i]=$sec_count
  282. echo "$line" | awk -F '|' -v clr1="${alert_color}" -v clr2="${secclr}" -v rs="${reset}" -v var="${importance}" '{printf "%-25s%s%-20s%s%-15s%-30s%s%s\n",$1,clr2,$2,clr1,var,$4,rs,$5}'
  283. let i++
  284. done
  285. secsums_total=$(echo $(printf "%d+" ${secsum[@]})0 | bc)
  286. echo -e "\nTotal:"
  287. printf "%-25s%s\n\n" "$count" "$secsums_total"
  288. printf "Check %s for more information.\n\n" "${provider}"
  289. fi
  290. }
  291. inputParser
  292. findMyPackageManager
  293. internetTest
  294. runTool