#!/bin/bash
#
# v.2 от 04/12/2023
#
# Скрипт выполняет массовое автоматизированное добавление Активов в систему Vulns.io VM по списку ip-адресов с привязкой УЗ к добавленному активу для безагентного сканирования
# Результатом работы скрипта является создание активов в интерфейсе on-premise с добавленной УЗ для безагентного сканирования
#
# скрипт использует пакеты curl и jq, в большинстве ОС они уже предустановлены
# Если пакет не установлен в операционной системе, например в Ubuntu, то его следует установить вручную командой:
# sudo apt install jq , либо командой:
# sudo dnf install jq
# допускается разместить пакет jq в папке со скриптом

# В параметре можно указать ID-учетной записи, которая будет привязана ко всем добавленным активам. При этом УЗ, указанные в csv-файлe использоваться не будут
# ./deploy-agentless_IP.sh <CREDENTIAL_ID>
# Пример:
# ./deploy-agentless.sh 5661788c-c237-41b1-9e72-be35f3dac6d9
#

# Допускать дубликаты активов или не допускать
# Если DOUBLE=1 (допускать дубликаты, не проверять наличие ранее созданных активов)
# Если DOUBLE=0 (не допускать дубликаты, скрипт должен проверить наличие ранее созданного актива с таким же IP-адресом
DOUBLE=1

# Ключ для доступа к API
API_KEY=""

# Исходный файл со списком IP-адресов
# Список Ip-адресов добавляется в 1-ю колонку
INPUT_FILENAME="deploy-agentless-template.csv"

# Файл с результатом работы скрипта 
OUTPUT_FILENAME="deploy-agentless-result.csv"

# IP адрес сервера OnPremise
VULNS_SERVER_IP=""

# Имя аккаунта в базе OnPremise
# По умолчанию accountid="0" - суперпользователь
ACCOUNT_ID=0

# Имя пользователя в базе OnPremise
# По умолчанию userid="0" - суперпользователь
USER_ID=0

# Временный файл.
FILE_TMP=${OUTPUT_FILENAME}_tmp

# Лог файл
LOG_FILE=${OUTPUT_FILENAME}.log

# Разрешено ли выводить информацию о действиях скрипта на экран или в лог-файл
LOG_TO_SCREEN=1
LOG_TO_FILE=0


# Комментарий
COMMENT=""

# Имя актива
HOSTNAME=""


out_data_log()
{
# Вывод информации о работе скрипта на экран или в лог-файл в зависимости от переменных LOG_TO_SCREEN и LOG_TO_FILE
if  [[ $LOG_TO_SCREEN -eq 1 ]]; then
  echo $1
fi
if  [[ $LOG_TO_FILE -eq 1 ]]; then
  echo $1 >> $LOG_FILE
fi
}


check_apikey()
{
# Проверка корректности API_KEY

out_data_log "Проверим API_KEY" #test

if [[ -n "$API_KEY" ]]; then

  GET_URL=$(curl -X GET -k "https://$VULNS_SERVER_IP/integration/v1/accounts" -H "x-api-key: $API_KEY" -H "Content-Type: application/json" --compressed -s)

  # Код HTTP ответа на запрос
  http_code=$(echo "$GET_URL"| $jq -r '.code')

  if [[ "$http_code" == null ]]; then

    # Проверим в ответе кол-во доступных организаций
    total=$(echo "$GET_URL"| $jq -r '.total') #test

    if [[ $total -gt 0 ]]; then
      out_data_log "API_KEY корректный"
    else
      out_data_log "Ошибка проверки API_KEY"
      exit
    fi

  else
    message=$(echo "$GET_URL"| jq -r '.message')
    out_data_log "Ошибка! $message"
    exit
  fi

else
  COMMENT="Ошибка! Не указан API_KEY"
  out_data_log "$COMMENT"
  exit
fi
}


check_os_type()
{
# Проверка корректности параметра OS_TYPE

 out_data_log "Проверим корректность типа актива($OS_TYPE)" #test

if [ "$OS_TYPE" != "linux" -a "$OS_TYPE" != "windows" ]; then

# Если OS_TYPE указан не верно 
  COMMENT="Тип актива указан не верно ($OS_TYPE)"
  out_data_log "Тип актива должен быть одним из:  [ linux / windows ] "

  #выйдем с ошибкой
  return 100 
else
  out_data_log "Тип актива указан корректно"
  return 0
fi

#out_data_log "Добавляем $OS_TYPE актив" 

}


check_credential()
{
# Проверка корректности и существования УЗ в CREDENTIAL_ID
# Проверка типа указанной УЗ и соответствия с Типом ОС добавляемых хостов в OS_TYPE

# Коды возврата
# =1 Тип УЗ (ssh) не подходит к активу
# =2 Тип УЗ (winrm) не подходит к активу
# =4 УЗ не найдена
# =100 Ошибка проверки УЗ

CRED_TYPE=""
CREDENTIAL_ID=`echo $CREDENTIAL_ID | sed 's/\r$//'`
out_data_log " Проверим УЗ($CREDENTIAL_ID)"
GET_URL=$(curl -X GET -k "https://$VULNS_SERVER_IP/integration/v1/credentials/$CREDENTIAL_ID" \
--header "Content-Type: application/json" \
--header "x-api-key: $API_KEY" --compressed -s \
)

# Узнаем тип УЗ м.б. [ssh / winrm]
CRED_TYPE=$(echo "$GET_URL"| $jq -r '.type')

# Проверка данных, возвращаемых в GET_URL
out_data_log "~~~debug message~~~"
out_data_log "cred_type = '$CRED_TYPE'"
out_data_log "url = '$GET_URL'"
out_data_log "~~~end of message~~~"
echo ""

# Проверим на соответствие типа УЗ и типа актива
case "$CRED_TYPE" in
 "ssh"              ) if [[ "$OS_TYPE" == "linux" ]]; then  out_data_log "УЗ для linux-актива корректная" 
                      elif [[ -n $OS_TYPE ]]; then
                        COMMENT="Тип УЗ ($CRED_TYPE) не подходит к активу ($OS_TYPE)"
                        out_data_log "$COMMENT"
                        return 1 
                      fi
                      ;;
 "winrm"            ) if [[ "$OS_TYPE" == "windows" ]]; then out_data_log "УЗ для  windows-актива корректная" 
                      elif [[ -n $OS_TYPE ]]; then 
                        COMMENT="Тип УЗ ($CRED_TYPE) не подходит к активу ($OS_TYPE)"
                        out_data_log "$COMMENT"
                        return 2 
                      fi
                      ;;
 "ResourceNotFound" ) COMMENT="Ошибка! УЗ $CREDENTIAL_ID не найдена"
                      out_data_log "$COMMENT"
                      CRED_TYPE=""
                      return 4 
                      ;;
                  * ) COMMENT="Ошибка проверки УЗ"
                      out_data_log "$COMMENT"
                      CRED_TYPE=""
                      return 100 
                      ;;
esac

}


check_asset_ip()
{

# Проверка наличия актива с указанным IP-адресом
#
# Код возврата
# =0 Актива с таким ip-адресом нет в системе
# =1 Актив с таким ip-адресом уже есть в системе, запомним его assetId
# =32 Не удалось проверить. Ошибка выполнения запроса

out_data_log "Проверим наличие ранее созданного актива и указанным ip-адресом($ASSET_IP)" #test

GET_URL=$(curl -X GET -k "https://$VULNS_SERVER_IP/integration/v1/assets?latestInventorization.ip=$ASSET_IP" -H "x-api-key: $API_KEY" -H "Content-Type: application/json" --compressed -s)

# Код HTTP ответа на запрос
http_code=$(echo "$GET_URL"| $jq -r '.code')

if [[ "$http_code" == null ]]; then

# Узнаем кол-во хостов
  total=$(echo "$GET_URL"| $jq -r '.total')

# Если кол-во хостов больше 0, то запомним  assetId первого найденого актива
  if [[ -n "$total" ]] && [[ $total != 0 ]]; then
    COMMENT="Актив с таким ip-адресом уже есть в системе"
    out_data_log "$COMMENT"

# Узнаем assetId первого найденного актива
    assetId=$(echo "$GET_URL"| $jq -r '.data[0].assetId')
    return 1

  else
    out_data_log "Актив с таким ip-адресом в системе не найден"

  fi

else
  COMMENT="Ошибка проверки ip-адреса актива - $http_code"
  out_data_log "$COMMENT"
  return 32
fi

}


check_validity_ip()
{
# Проверка валидности ip-адреса

out_data_log "Проверим валидность ip-адреса($ASSET_IP)" #test

echo "$ASSET_IP"| grep -Eq '^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?)$'

if [[ $? -eq 0 ]]; then
  out_data_log "IP-адрес ($ASSET_IP) - валидный"
  return 0
else
  COMMENT="Ошибка! Некорректный IP-адрес ($ASSET_IP)"
  out_data_log "$COMMENT"
  return 1
fi
}


add_asset()
{
# Отправка запроса на добавление актива
# ASSET_IP получаем при чтении файла из 

out_data_log "Добавим новый актив ($ASSET_IP)" #test

GET_URL=$(curl -X GET -k "https://$VULNS_SERVER_IP/integration/v1/agents/download/installer/url?osType=$OS_TYPE&accountId=$ACCOUNT_ID&userId=$USER_ID&ip=$ASSET_IP" -H "Content-Type: application/json" -H "x-api-key: $API_KEY" --compressed -s)

# Код HTTP ответа на запрос
http_code=$(echo "$GET_URL"| $jq -r '.code')

# Получим ID-актива из ответа
if [[ "$http_code" == null ]]; then
  assetId=$(echo "$GET_URL"| $jq -r '.assetId')
  COMMENT="Актив успешно добавлен"
  out_data_log "Актив $assetId успешно добавлен"
else
  COMMENT="Ошибка! Не удалось добавить актив"
  out_data_log "$COMMENT"

 # Обнулим assetId в случае неудачи  
  assetId=""
  return 32

fi

}


add_credentials()
{
# Отправка запроса на добавление УЗ (CREDENTIAL_ID) к созданному активу
# assetId получаем из ответа предыдущего запроса 

out_data_log "Добавим УЗ к активу (CREDENTIAL_ID=$CREDENTIAL_ID; assetId=$assetId)" #test

GET_URL=$(curl -X --location --request PATCH -k "https://$VULNS_SERVER_IP/integration/v1/assets/$assetId" \
--header "Content-Type: application/json" \
--header "x-api-key: $API_KEY" \
--data '{
    "accountId": "'$ACCOUNT_ID'",
    "assetGroupIds": null,
    "credentialId": "'$CREDENTIAL_ID'",
    "type": "host",
    "hostname": "'$HOSTNAME'"
}' -s compressed)

# Код HTTP ответа на запрос
http_code=$(echo "$GET_URL"| $jq -r '.code')

if [[ "$http_code" == null ]]; then
  out_data_log "УЗ $CREDENTIAL_ID успешно добавлена к активу $assetId"
else
# Обновим комментарий
  COMMENT="Ошибка! Не удалось добавить УЗ к активу"
  out_data_log "$COMMENT"
  return 32
fi

# Конец add_credentials()
}


write_comment_to_file()
{
# Добавим комментарий в файл
# 1-я колонка - IP-адрес хоста
# 2-я колонка - Результат
# 3-я колонка - Тип актива [ linux / windows]
# 4-я колонка - ID-созданного актива или ""
# 5-я колонка - ID-добавленной к активу учетной записи
# 6-я колонка - Тип добавленной УЗ [ ssh / winrm ]
# 7-я колонка - Имя хоста

#out_data_log "Запишем в файл $FILE_TMP $ASSET_IP; $OS_TYPE; $assetId; $CREDENTIAL_ID; $CRED_TYPE; $COMMENT; $HOSTNAME"

echo "$ASSET_IP;$OS_TYPE;$assetId;$CREDENTIAL_ID;$CRED_TYPE;$COMMENT;$HOSTNAME" >> $FILE_TMP

}



############## --------- MAIN ---------#######################################

# Проверим установлены ли пакеты curl и jq
# Если curl не установлен - выходим с ошибкой
# Если jq не установлен, пробуем запустить локальный пакет из текущей папки ./jq
# Если и в текущей папке нет jq, то выходим с ошибкой
if ! command -v curl &>/dev/null; then
  out_data_log "Ошибка: пакет curl не установлен"
  out_data_log "Установите curl командой sudo apt install curl "
  exit

elif command -v jq &>/dev/null; then
    # определим переменную $jq
    jq='jq'
    out_data_log "jq установлен"

  elif ! command -v ./jq &>/dev/null; then
    out_data_log "Ошибка: пакет jq не установлен"
    out_data_log "Установите 'jq' командой sudo apt install jq "
    exit

  else
    jq='./jq'
    out_data_log "Используем пакет jq из текущей папки"
fi


# Проверим доступность сервера VULNS_SERVER_IP
curl -I -k "https://$VULNS_SERVER_IP" -s &> /dev/null

[ $? -ne 0 ] && out_data_log "Ошибка! Сервер $VULNS_SERVER_IP недоступен" && exit


# Проверим корректность API_KEY
check_apikey

# Получим УЗ из внешнего параметра если есть и проверим её
CRED_FROM_PARAM=$1

#   echo "CRED_FROM_PARAM=$CRED_FROM_PARAM" #test

if [[ -n "$CRED_FROM_PARAM" ]]; then

  CREDENTIAL_ID=$CRED_FROM_PARAM

  check_credential
  if [[ $? -gt 3 ]]; then 
# Если код возврата больше 3, то УЗ использовать нельзя -> прекращаем работу скрипта
    out_data_log "Выходим!" 
    exit
  fi
fi


# Проверим наличие исходного файла INPUT_FILENAME
# Если исходного файла нет - прекращаем работу
[[ ! -e $INPUT_FILENAME ]] && echo "Файл $INPUT_FILENAME не найден" && exit



# Читаем исходный файл
#
# в переменную $ASSET_IP построчно будут записыватся данные из файла (IP-адреса)
n=0 # Временная переменная для пропуска заголовка

while IFS=';' read ASSET_IP HOSTNAME OS_TYPE CREDENTIAL_ID
do
  echo ""
  #echo "debug"
  #echo "$ASSET_IP $HOSTNAME ($OS_TYPE) ($CREDENTIAL_ID)"
# Пропускаем чтение заголовка исходного файла
  [ $n == 0 ] && n=$((n+1)) && continue

# Обнулим комментарий
  COMMENT=""

# Обнулим ID-актива, чтобы не остался после предыдущей итерации
  assetId=""

# Если УЗ была указана в параметре, то используем её
# Если УЗ не была указана в параметре, то используем CREDENTIAL_ID из файла
  [ -n "$CRED_FROM_PARAM" ] && CREDENTIAL_ID=$CRED_FROM_PARAM


# Проверим валидность ip-адреса
# Если ip-адрес невалиный - пропустим итерацию и запишем коммент в итоговый файл
  check_validity_ip
  [ $? -ne 0 ] && write_comment_to_file && continue


# Если DOUBLE=0 (не допускать дубликаты, скрипт должен проверить наличие ранее созданного актива с таким же IP-адресом
  if [ $DOUBLE == 0 ]; then

    check_asset_ip
    # assetId будет получен из check_asset_ip (если такой актив уже есть)

    # Если Актив уже есть, то добавим комментарий в итоговый файл и перейдем к следующей итерации
    [ $? -ne 0 ] && write_comment_to_file && continue
  fi


# Проверим корректность типа актива из файла
  check_os_type
# Если тип актива указан некорректно, то добавим комментарий в итоговый файл и перейдем к следующей итерации
  [ $? -ne 0 ] && write_comment_to_file && continue


# Проверим УЗ из исходного файла..
  check_credential
# Если УЗ указана некорректно, то добавим комментарий в итоговый файл и перейдем к следующей итерации  
  [ $? -ne 0 ] && write_comment_to_file && continue


# Если ранее из check_asset_ip не получен assetId, т.е. актива с таким ip-адресом нет в системе
# то просто добавим новый актив
  [ -z $assetId ] && add_asset


  # Если актив добавить не удалось, переходим к следующей записи
  [ $? -ne 0 ] && write_comment_to_file && continue


  # Добавим УЗ к активу
  add_credentials

  [ $? -eq 0 ] && COMMENT="Успешно" && write_comment_to_file


  echo ""

done < <(cat ./$INPUT_FILENAME)
# Конец обработки исходного файла


# Добавим строку с заголовками столбцов в результирующий файл
echo "IP-адрес;Тип актива;ID-актива;ID-учетной записи;Тип УЗ;Результат;Имя хоста;" >> $OUTPUT_FILENAME

# Перепишем временный файл в Файл с результатом работы скрипта
cat $FILE_TMP >> $OUTPUT_FILENAME

# Удалим временный файл
rm -f ./$FILE_TMP

################ Конец скрипта ###############################

