mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-11 23:35:07 +00:00
545 lines
16 KiB
Bash
Executable File
545 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
# Prowler is a tool that provides automate auditing and hardening guidance of an AWS account.
|
||
# It is based on AWS-CLI commands. It follows guidelines present in the CIS Amazon
|
||
# Web Services Foundations Benchmark at:
|
||
# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf
|
||
|
||
# This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0
|
||
# International Public License. The link to the license terms can be found at
|
||
# https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
|
||
#
|
||
# Author: Toni de la Fuente - @ToniBlyx - https://blyx.com/contact
|
||
|
||
# Prowler - Iron Maiden
|
||
#
|
||
# Walking through the city, looking oh so pretty
|
||
# I've just got to find my way
|
||
# See the ladies flashing
|
||
# All there legs and lashes
|
||
# I've just got to find my way...
|
||
|
||
OPTRED="[1;31m"
|
||
OPTNORMAL="[0;39m"
|
||
|
||
# Set the defaults for these getopts variables
|
||
REGION="us-east-1"
|
||
FILTERREGION=""
|
||
MAXITEMS=100
|
||
MONOCHROME=0
|
||
MODE="text"
|
||
SEP=','
|
||
KEEPCREDREPORT=0
|
||
EXITCODE=0
|
||
|
||
# Command usage menu
|
||
usage(){
|
||
echo "
|
||
USAGE:
|
||
`basename $0` -p <profile> -r <region> [ -h ]
|
||
|
||
Options:
|
||
-p <profile> specify your AWS profile to use (i.e.: default)
|
||
-r <region> specify an AWS region to direct API requests to
|
||
(i.e.: us-east-1), all regions are checked anyway
|
||
-c <check_id> specify a check number or group from the AWS CIS benchmark
|
||
(i.e.: "check11" for check 1.1, "check3" for entire section 3, "level1" for CIS Level 1 Profile Definitions or "forensics-ready")
|
||
-f <filterregion> specify an AWS region to run checks against
|
||
(i.e.: us-west-1)
|
||
-m <maxitems> specify the maximum number of items to return for long-running requests (default: 100)
|
||
-M <mode> output mode: text (defalut), mono, csv (separator is ","; data is on stdout; progress on stderr)
|
||
-k keep the credential report
|
||
-n show check numbers to sort easier
|
||
(i.e.: 1.01 instead of 1.1)
|
||
-l list all available checks only (does not perform any check)
|
||
-e exclude extras
|
||
-h this help
|
||
"
|
||
exit
|
||
}
|
||
|
||
while getopts ":hlkp:r:c:f:m:M:en" OPTION; do
|
||
case $OPTION in
|
||
h )
|
||
usage
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
;;
|
||
l )
|
||
PRINTCHECKSONLY=1
|
||
;;
|
||
k )
|
||
KEEPCREDREPORT=1
|
||
;;
|
||
p )
|
||
PROFILE=$OPTARG
|
||
;;
|
||
r )
|
||
REGION=$OPTARG
|
||
;;
|
||
c )
|
||
CHECKNUMBER=$OPTARG
|
||
;;
|
||
f )
|
||
FILTERREGION=$OPTARG
|
||
;;
|
||
m )
|
||
MAXITEMS=$OPTARG
|
||
;;
|
||
M )
|
||
MODE=$OPTARG
|
||
;;
|
||
n )
|
||
NUMERAL=1
|
||
;;
|
||
e )
|
||
EXTRAS=1
|
||
;;
|
||
: )
|
||
echo ""
|
||
echo "$OPTRED ERROR!$OPTNORMAL -$OPTARG requires an argument"
|
||
usage
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
;;
|
||
? )
|
||
echo ""
|
||
echo "$OPTRED ERROR!$OPTNORMAL Invalid option"
|
||
usage
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
;;
|
||
esac
|
||
done
|
||
|
||
. include/colors
|
||
|
||
SCRIPT_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" )
|
||
|
||
# Functions to manage dates depending on OS
|
||
if [ "$OSTYPE" == "linux-gnu" ] || [ "$OSTYPE" == "linux-musl" ]; then
|
||
TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX)
|
||
# function to compare in days, usage how_older_from_today date
|
||
# date format %Y-%m-%d
|
||
how_older_from_today()
|
||
{
|
||
DATE_TO_COMPARE=$1
|
||
TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
|
||
DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
|
||
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
|
||
echo $DAYS_SINCE
|
||
}
|
||
# function to convert from timestamp to date, usage timestamp_to_date timestamp
|
||
# output date format %Y-%m-%d
|
||
timestamp_to_date()
|
||
{
|
||
# remove fractions of a second
|
||
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
|
||
OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
|
||
echo $OUTPUT_DATE
|
||
}
|
||
decode_report()
|
||
{
|
||
base64 -d
|
||
}
|
||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||
# BSD/OSX commands compatibility
|
||
TEMP_REPORT_FILE=$(mktemp -t prowler.cred_report-XXXXXX)
|
||
how_older_from_today()
|
||
{
|
||
DATE_TO_COMPARE=$1
|
||
TODAY_IN_DAYS=$(date +%s)
|
||
DATE_FROM_IN_DAYS=$(date -jf %Y-%m-%d $DATE_TO_COMPARE +%s)
|
||
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
|
||
echo $DAYS_SINCE
|
||
}
|
||
timestamp_to_date()
|
||
{
|
||
# remove fractions of a second
|
||
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
|
||
OUTPUT_DATE=$(date -r $TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
|
||
echo $OUTPUT_DATE
|
||
}
|
||
decode_report()
|
||
{
|
||
base64 -D
|
||
}
|
||
elif [[ "$OSTYPE" == "cygwin" ]]; then
|
||
# POSIX compatibility layer and Linux environment emulation for Windows
|
||
TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX)
|
||
how_older_from_today()
|
||
{
|
||
DATE_TO_COMPARE=$1
|
||
TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
|
||
DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
|
||
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
|
||
echo $DAYS_SINCE
|
||
}
|
||
timestamp_to_date()
|
||
{
|
||
# remove fractions of a second
|
||
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
|
||
OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
|
||
echo $OUTPUT_DATE
|
||
}
|
||
decode_report()
|
||
{
|
||
base64 -d
|
||
}
|
||
else
|
||
echo "Unknown Operating System! Valid \$OSTYPE: linux-gnu, linux-musl, darwin* or cygwin"
|
||
echo "Found: $OSTYPE"
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
fi
|
||
|
||
# It checks -p optoin first and use it as profile, if not -p provided then
|
||
# check environment variables and if not, it checks and loads credentials from
|
||
# instance profile (metadata server) if runs in an EC2 instance
|
||
|
||
if [[ $PROFILE ]]; then
|
||
PROFILE_OPT="--profile $PROFILE"
|
||
else
|
||
# if Prowler runs insinde an AWS instance with IAM instance profile attached
|
||
INSTANCE_PROFILE=$(curl -s -m 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/)
|
||
if [[ $INSTANCE_PROFILE ]]; then
|
||
AWS_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g')
|
||
AWS_SECRET_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g')
|
||
AWS_SESSION_TOKEN=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g')
|
||
fi
|
||
if [[ $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY || $AWS_SESSION_TOKEN ]];then
|
||
PROFILE="ENV"
|
||
PROFILE_OPT=""
|
||
else
|
||
PROFILE="default"
|
||
PROFILE_OPT="--profile $PROFILE"
|
||
fi
|
||
fi
|
||
|
||
# AWS-CLI variables
|
||
AWSCLI=$(which aws)
|
||
if [ -z "${AWSCLI}" ]; then
|
||
echo -e "\n$RED ERROR!$NORMAL AWS-CLI (aws command) not found. Make sure it is installed correctly and in your \$PATH\n"
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
fi
|
||
|
||
TITLE_ID=""
|
||
TITLE_TEXT="CALLER ERROR - UNSET TITLE"
|
||
|
||
## Output formatting functions
|
||
textOK(){
|
||
if [[ "$MODE" == "csv" ]]; then
|
||
if [[ $2 ]]; then
|
||
REPREGION=$2
|
||
else
|
||
REPREGION=$REGION
|
||
fi
|
||
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
|
||
else
|
||
echo " $OK OK! $NORMAL $1"
|
||
fi
|
||
}
|
||
|
||
textNotice(){
|
||
if [[ "$MODE" == "csv" ]]; then
|
||
if [[ $2 ]]; then
|
||
REPREGION=$2
|
||
else
|
||
REPREGION=$REGION
|
||
fi
|
||
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}INFO${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
|
||
else
|
||
echo " $NOTICE INFO! $1 $NORMAL"
|
||
fi
|
||
}
|
||
|
||
textWarn(){
|
||
EXITCODE=3
|
||
if [[ "$MODE" == "csv" ]]; then
|
||
if [[ $2 ]]; then
|
||
REPREGION=$2
|
||
else
|
||
REPREGION=$REGION
|
||
fi
|
||
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}WARNING${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
|
||
else
|
||
echo " $BAD WARNING! $1 $NORMAL"
|
||
fi
|
||
}
|
||
|
||
textTitle(){
|
||
TITLE_ID=$1
|
||
if [[ $NUMERAL ]]; then
|
||
TITLE_ID=$(echo $TITLE_ID | cut -d, -f2)
|
||
else
|
||
TITLE_ID=$(echo $TITLE_ID | cut -d, -f1)
|
||
fi
|
||
|
||
TITLE_TEXT=$2
|
||
|
||
case "$3" in
|
||
0|No|NOT_SCORED)
|
||
ITEM_SCORED="Not Scored"
|
||
;;
|
||
1|Yes|SCORED)
|
||
ITEM_SCORED="Scored"
|
||
;;
|
||
*)
|
||
ITEM_SCORED="Unspecified"
|
||
;;
|
||
esac
|
||
|
||
case "$4" in
|
||
LEVEL1) ITEM_LEVEL="Level 1";;
|
||
LEVEL2) ITEM_LEVEL="Level 2";;
|
||
EXTRA) ITEM_LEVEL="Extra";;
|
||
SUPPORT) ITEM_LEVEL="Support";;
|
||
*) ITEM_LEVEL="Unspecified or Invalid";;
|
||
esac
|
||
|
||
if [[ "$MODE" == "csv" ]]; then
|
||
>&2 echo "$TITLE_ID $TITLE_TEXT"
|
||
else
|
||
if [[ "$ITEM_SCORED" == "Scored" ]]; then
|
||
echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT"
|
||
else
|
||
echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
printCsvHeader() {
|
||
>&2 echo ""
|
||
>&2 echo "Generating \"${SEP}\" delimited report on stdout for profile $PROFILE, account $ACCOUNT_NUM"
|
||
echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}RESULT${SEP}SCORED${SEP}LEVEL${SEP}TITLE_TEXT${SEP}NOTES"
|
||
}
|
||
|
||
prowlerBanner() {
|
||
echo -e "$CYAN _"
|
||
echo -e " _ __ _ __ _____ _| | ___ _ __"
|
||
echo -e " | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__|"
|
||
echo -e " | |_) | | | (_) \ V V /| | __/ |"
|
||
echo -e " | .__/|_| \___/ \_/\_/ |_|\___|_|"
|
||
echo -e " |_|$NORMAL$BLUE CIS based AWS Account Hardening Tool$NORMAL\n"
|
||
echo -e "$YELLOW Date: $(date)"
|
||
}
|
||
|
||
# Get whoami in AWS, who is the user running this shell script
|
||
getWhoami(){
|
||
ACCOUNT_NUM=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Account" | tr -d '"')
|
||
if [[ "$MODE" == "csv" ]]; then
|
||
CALLER_ARN_RAW=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Arn")
|
||
if [[ 255 -eq $? ]]; then
|
||
# Failed to get own identity ... exit
|
||
echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
|
||
>&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
|
||
EXITCODE=2
|
||
exit $EXITCODE
|
||
fi
|
||
CALLER_ARN=$(echo $CALLER_ARN_RAW | tr -d '"')
|
||
printCsvHeader
|
||
textTitle "0.0" "Show report generation info" "NOT_SCORED" "SUPPORT"
|
||
textNotice "ARN: $CALLER_ARN TIMESTAMP: $SCRIPT_START_TIME"
|
||
else
|
||
echo ""
|
||
echo "This report is being generated using credentials below:"
|
||
echo ""
|
||
echo -e "AWS-CLI Profile: $NOTICE[$PROFILE]$NORMAL AWS API Region: $NOTICE[$REGION]$NORMAL AWS Filter Region: $NOTICE[${FILTERREGION:-all}]$NORMAL\n"
|
||
if [[ $MONOCHROME -eq 1 ]]; then
|
||
echo "Caller Identity:"
|
||
$AWSCLI sts get-caller-identity --output text $PROFILE_OPT --region $REGION --query "Arn"
|
||
if [[ 255 -eq $? ]]; then
|
||
# Failed to get own identity ... exit
|
||
echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
|
||
>&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
|
||
exit 2
|
||
fi
|
||
echo ""
|
||
else
|
||
echo "Caller Identity:"
|
||
$AWSCLI sts get-caller-identity --output table $PROFILE_OPT --region $REGION
|
||
if [[ 255 -eq $? ]]; then
|
||
# Failed to get own identity ... exit
|
||
echo variable $PROFILE_OPT
|
||
echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
|
||
>&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
|
||
EXITCODE=2
|
||
exit $EXITCODE
|
||
fi
|
||
echo ""
|
||
fi
|
||
fi
|
||
}
|
||
|
||
printColorsCode(){
|
||
if [[ $MONOCHROME -eq 0 ]]; then
|
||
echo -e "\nColors Code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL \n"
|
||
fi
|
||
}
|
||
|
||
# Generate Credential Report
|
||
genCredReport() {
|
||
textTitle "0.1" "Generating AWS IAM Credential Report..." "NOT_SCORED" "SUPPORT"
|
||
until $( $AWSCLI iam generate-credential-report --output text --query 'State' $PROFILE_OPT --region $REGION |grep -q -m 1 "COMPLETE") ; do
|
||
sleep 1
|
||
done
|
||
}
|
||
|
||
# Save report to a file, decode it, deletion at finish and after every single check
|
||
saveReport(){
|
||
$AWSCLI iam get-credential-report --query 'Content' --output text $PROFILE_OPT --region $REGION | decode_report > $TEMP_REPORT_FILE
|
||
if [[ $KEEPCREDREPORT -eq 1 ]]; then
|
||
textTitle "0.2" "Saving IAM Credential Report ..." "NOT_SCORED" "SUPPORT"
|
||
textNotice "IAM Credential Report saved in $TEMP_REPORT_FILE"
|
||
fi
|
||
}
|
||
|
||
# Delete temporary report file
|
||
cleanTemp(){
|
||
if [[ $KEEPCREDREPORT -ne 1 ]]; then
|
||
rm -fr $TEMP_REPORT_FILE
|
||
fi
|
||
}
|
||
|
||
# Delete the temporary report file if we get interrupted/terminated
|
||
trap cleanTemp EXIT
|
||
|
||
# Get a list of all available AWS Regions
|
||
REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \
|
||
--output text \
|
||
$PROFILE_OPT \
|
||
--region $REGION \
|
||
--region-names $FILTERREGION)
|
||
|
||
infoReferenceLong(){
|
||
# Report review note:
|
||
echo -e ""
|
||
echo -e "For more information on the Prowler, feedback and issue reporting:"
|
||
echo -e "https://github.com/Alfresco/prowler"
|
||
echo -e ""
|
||
echo -e "For more information on the CIS benchmark:"
|
||
echo -e "https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf"
|
||
|
||
}
|
||
|
||
callCheck(){
|
||
if [[ $CHECKNUMBER ]];then
|
||
case "$CHECKNUMBER" in
|
||
check11|check101 ) execute_check check11;;
|
||
* )
|
||
textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n";
|
||
esac
|
||
cleanTemp
|
||
exit $EXITCODE
|
||
fi
|
||
}
|
||
|
||
# List only check tittles
|
||
|
||
if [[ $PRINTCHECKSONLY == "1" ]]; then
|
||
prowlerBanner
|
||
show_all_titles
|
||
exit $EXITCODE
|
||
fi
|
||
|
||
# Include all of the groups of checks inside include folder
|
||
for group in $(ls groups/group*); do
|
||
. "$group"
|
||
done
|
||
|
||
# Include all of the checks inside include folder
|
||
# this includes also extra check since they are "check_extraNN"
|
||
for checks in $(ls checks/check*); do
|
||
. "$checks"
|
||
done
|
||
|
||
# Function to show the title of the check
|
||
show_check_title() {
|
||
# This would just call textTitle
|
||
textTitle "${CHECK_ID[$1]}" "${CHECK_TITLE[$1]}" "${CHECK_SCORED[$1]}" "${CHECK_TYPE[$1]}"
|
||
}
|
||
|
||
# Function to show the title of a group, by numeric id
|
||
show_group_title() {
|
||
# This would also just call textTitle in the real prowler
|
||
if [[ "$MODE" != "csv" ]]; then
|
||
textTitle "${GROUP_NUMBER[$1]}" "${GROUP_TITLE[$1]}" "NOT_SCORED" "SUPPORT"
|
||
fi
|
||
}
|
||
|
||
# Function to execute the check
|
||
execute_check() {
|
||
# See if this is an alternate name for a check
|
||
# for example, we might have been passed 1.01 which is another name for 1.1
|
||
if [ ${CHECK_ALTERNATE[$1]} ];then
|
||
show_check_title ${CHECK_ALTERNATE[$1]}
|
||
${CHECK_ALTERNATE[$1]}
|
||
else
|
||
show_check_title $1
|
||
$1
|
||
fi
|
||
}
|
||
|
||
# Function to execute all checks in a group
|
||
execute_group() {
|
||
show_group_title $1
|
||
# run the checks in the group
|
||
IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$1]}
|
||
for i in "${CHECKS[@]}"; do
|
||
execute_check $i
|
||
done
|
||
}
|
||
|
||
# Function to execute group by name
|
||
execute_group_by_name() {
|
||
for i in ${#GROUP_NAME[@]}; do
|
||
if [ "${GROUP_NAME[$i]}" == "$1" ]; then
|
||
execute_group $i
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Function to execute all checks in all groups
|
||
execute_all() {
|
||
for i in ${#GROUP_TITLE[@]}; do
|
||
if [ "${GROUP_RUN_BY_DEFAULT[$i]}" == "Y" ]; then
|
||
execute_group $i
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Function to show the titles of everything
|
||
show_all_titles() {
|
||
for i in ${#GROUP_TITLE[@]}; do
|
||
show_group_title $i
|
||
# Display the title of the checks
|
||
IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$i]}
|
||
for j in "${CHECKS[@]}"; do
|
||
show_check_title $j
|
||
done
|
||
done
|
||
}
|
||
|
||
### All functions defined above ... run the workflow
|
||
|
||
if [[ $MODE != "csv" ]]; then
|
||
prowlerBanner
|
||
printColorsCode
|
||
fi
|
||
getWhoami
|
||
genCredReport
|
||
saveReport
|
||
callCheck
|
||
|
||
show_all_titles
|
||
|
||
|
||
# if [[ ! $EXTRAS ]]; then
|
||
# textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT"
|
||
# execute_group 7
|
||
# fi
|
||
|
||
cleanTemp
|
||
exit $EXITCODE
|