Created
April 24, 2023 21:34
-
-
Save alexverboon/10dccce343e58a480d213600a451ef07 to your computer and use it in GitHub Desktop.
MDE Linux Installer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
#============================================================================ | |
# | |
# Copyright (c) 2021 Microsoft Corporation. All rights reserved. | |
# | |
# Abstract: | |
# MDE installation script | |
# - Fingerprinting OS and manually installs MDE as described in the online documentation | |
# https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/linux-install-manually?view=o365-worldwide | |
# - Runs additional optional checks: minimal requirements, fanotify subscribers, etc. | |
# | |
#============================================================================ | |
SCRIPT_VERSION="0.6.2" | |
ASSUMEYES=-y | |
CHANNEL=insiders-fast | |
DISTRO= | |
DISTRO_FAMILY= | |
PKG_MGR= | |
INSTALL_MODE= | |
DEBUG= | |
VERBOSE= | |
MDE_VERSION_CMD="mdatp health --field app_version" | |
PMC_URL=https://packages.microsoft.com/config | |
SCALED_VERSION= | |
VERSION= | |
ONBOARDING_SCRIPT="MicrosoftDefenderATPOnboardingLinuxServer.py" | |
OFFBOARDING_SCRIPT= | |
MIN_REQUIREMENTS= | |
SKIP_CONFLICTING_APPS= | |
PASSIVE_MODE= | |
MIN_CORES=2 | |
MIN_MEM_MB=2048 | |
MIN_DISK_SPACE_MB=1280 | |
declare -a tags | |
# Error codes | |
SUCCESS=0 | |
ERR_INTERNAL=1 | |
ERR_INVALID_ARGUMENTS=2 | |
ERR_INSUFFICIENT_PRIVILAGES=3 | |
ERR_NO_INTERNET_CONNECTIVITY=4 | |
ERR_CONFLICTING_APPS=5 | |
ERR_UNSUPPORTED_DISTRO=10 | |
ERR_UNSUPPORTED_VERSION=11 | |
ERR_INSUFFICIENT_REQUIREMENTS=12 | |
ERR_CORRUPT_MDE_INSTALLED=15 | |
ERR_MDE_NOT_INSTALLED=20 | |
ERR_INSTALLATION_FAILED=21 | |
ERR_UNINSTALLATION_FAILED=22 | |
ERR_FAILED_DEPENDENCY=23 | |
ERR_FAILED_REPO_SETUP=24 | |
ERR_INVALID_CHANNEL=25 | |
ERR_FAILED_REPO_CLEANUP=26 | |
ERR_ONBOARDING_NOT_FOUND=30 | |
ERR_ONBOARDING_FAILED=31 | |
ERR_OFFBOARDING_NOT_FOUND=32 | |
ERR_OFFBOARDING_FAILED=33 | |
ERR_TAG_NOT_SUPPORTED=40 | |
ERR_PARAMETER_SET_FAILED=41 | |
ERR_UNSUPPORTED_ARCH=45 | |
# Predefined values | |
export DEBIAN_FRONTEND=noninteractive | |
_log() { | |
level="$1" | |
dest="$2" | |
msg="${@:3}" | |
ts=$(date -u +"%Y-%m-%dT%H:%M:%S") | |
if [ "$dest" = "stdout" ]; then | |
echo "$msg" | |
elif [ "$dest" = "stderr" ]; then | |
>&2 echo "$msg" | |
fi | |
if [ -n "$log_path" ]; then | |
echo "$ts $level $msg" >> "$log_path" | |
fi | |
} | |
log_debug() { | |
_log "DEBUG" "stdout" "$@" | |
} | |
log_info() { | |
_log "INFO " "stdout" "$@" | |
} | |
log_warning() { | |
_log "WARN " "stderr" "$@" | |
} | |
log_error() { | |
_log "ERROR" "stderr" "$@" | |
} | |
script_exit() | |
{ | |
if [ -z "$1" ]; then | |
log_error "[!] INTERNAL ERROR. script_exit requires an argument" | |
exit $ERR_INTERNAL | |
fi | |
if [ -n $DEBUG ]; then | |
print_state | |
fi | |
if [ "$2" = "0" ]; then | |
log_info "[v] $1" | |
else | |
log_error "[x] $1" | |
fi | |
if [ -z "$2" ]; then | |
exit $ERR_INTERNAL | |
elif ! [ "$2" -eq "$2" ] 2> /dev/null; then #check error is number | |
exit $ERR_INTERNAL | |
else | |
log_info "[*] exiting ($2)" | |
exit $2 | |
fi | |
} | |
get_python() { | |
if which python3 &> /dev/null; then | |
echo "python3" | |
elif which python2 &> /dev/null; then | |
echo "python2" | |
else | |
echo "python" | |
fi | |
} | |
parse_uri() { | |
cat <<EOF | /usr/bin/env $(get_python) | |
import sys | |
if sys.version_info < (3,): | |
from urlparse import urlparse | |
else: | |
from urllib.parse import urlparse | |
uri = urlparse("$1") | |
print(uri.scheme or "") | |
print(uri.hostname or "") | |
print(uri.port or "") | |
EOF | |
} | |
get_rpm_proxy_params() { | |
proxy_params="" | |
if [ -n "$http_proxy" ]; then | |
proxy_host=$(parse_uri "$http_proxy" | sed -n '2p') | |
if [ -n "$proxy_host" ];then | |
proxy_params="$proxy_params --httpproxy $proxy_host" | |
fi | |
proxy_port=$(parse_uri "$http_proxy" | sed -n '3p') | |
if [ -n "$proxy_port" ]; then | |
proxy_params="$proxy_params --httpport $proxy_port" | |
fi | |
fi | |
if [ -n "$ftp_proxy" ];then | |
proxy_host=$(parse_uri "$ftp_proxy" | sed -n '2p') | |
if [ -n "$proxy_host" ];then | |
proxy_params="$proxy_params --ftpproxy $proxy_host" | |
fi | |
proxy_port=$(parse_uri "$ftp_proxy" | sed -n '3p') | |
if [ -n "$proxy_port" ]; then | |
proxy_params="$proxy_params --ftpport $proxy_port" | |
fi | |
fi | |
echo $proxy_params | |
} | |
run_quietly() | |
{ | |
# run_quietly <command> <error_msg> [<error_code>] | |
# use error_code for script_exit | |
if [ $# -lt 2 ] || [ $# -gt 3 ]; then | |
log_error "[!] INTERNAL ERROR. run_quietly requires 2 or 3 arguments" | |
exit 1 | |
fi | |
local out=$(eval $1 2>&1; echo "$?") | |
local exit_code=$(echo "$out" | tail -n1) | |
if [ -n "$VERBOSE" ]; then | |
log_info "$out" | |
fi | |
if [ "$exit_code" -ne 0 ]; then | |
if [ -n $DEBUG ]; then | |
log_debug "[>] Running command: $1" | |
log_debug "[>] Command output: $out" | |
log_debug "[>] Command exit_code: $exit_code" | |
fi | |
if [ $# -eq 2 ]; then | |
log_error $2 | |
else | |
script_exit "$2" "$3" | |
fi | |
fi | |
return $exit_code | |
} | |
retry_quietly() | |
{ | |
# retry_quietly <retries> <command> <error_msg> [<error_code>] | |
# use error_code for script_exit | |
if [ $# -lt 3 ] || [ $# -gt 4 ]; then | |
log_error "[!] INTERNAL ERROR. retry_quietly requires 3 or 4 arguments" | |
exit 1 | |
fi | |
local exit_code= | |
local retries=$1 | |
while [ $retries -gt 0 ] | |
do | |
if run_quietly "$2" "$3"; then | |
exit_code=0 | |
else | |
exit_code=1 | |
fi | |
if [ $exit_code -ne 0 ]; then | |
sleep 1 | |
((retries--)) | |
log_info "[r] $(($1-$retries))/$1" | |
else | |
retries=0 | |
fi | |
done | |
if [ $# -eq 4 ] && [ $exit_code -ne 0 ]; then | |
script_exit "$3" "$4" | |
fi | |
return $exit_code | |
} | |
print_state() | |
{ | |
if [ -z $(which mdatp) ]; then | |
log_warning "[S] MDE not installed." | |
else | |
log_info "[S] MDE installed." | |
if run_quietly "mdatp health" "[S] Could not connect to the daemon -- MDE is not ready to connect yet."; then | |
log_info "[S] Version: $($MDE_VERSION_CMD)" | |
log_info "[S] Onboarded: $(mdatp health --field licensed)" | |
log_info "[S] Passive mode: $(mdatp health --field passive_mode_enabled)" | |
log_info "[S] Device tags: $(mdatp health --field edr_device_tags)" | |
log_info "[S] Subsystem: $(mdatp health --field real_time_protection_subsystem)" | |
log_info "[S] Conflicting applications: $(mdatp health --field conflicting_applications)" | |
fi | |
fi | |
} | |
detect_arch() | |
{ | |
arch=$(uname -m) | |
if [[ "$arch" =~ arm* ]]; then | |
script_exit "ARM architecture is not yet supported by the script" $ERR_UNSUPPORTED_ARCH | |
fi | |
} | |
detect_distro() | |
{ | |
if [ -f /etc/os-release ] || [ -f /etc/mariner-release ]; then | |
. /etc/os-release | |
DISTRO=$ID | |
VERSION=$VERSION_ID | |
VERSION_NAME=$VERSION_CODENAME | |
elif [ -f /etc/redhat-release ]; then | |
if [ -f /etc/oracle-release ]; then | |
DISTRO="ol" | |
elif [[ $(grep -o -i "Red\ Hat" /etc/redhat-release) ]]; then | |
DISTRO="rhel" | |
elif [[ $(grep -o -i "Centos" /etc/redhat-release) ]]; then | |
DISTRO="centos" | |
fi | |
VERSION=$(grep -o "release .*" /etc/redhat-release | cut -d ' ' -f2) | |
else | |
script_exit "unable to detect distro" $ERR_UNSUPPORTED_DISTRO | |
fi | |
# change distro to ubuntu for linux mint support | |
if [ "$DISTRO" == "linuxmint" ]; then | |
DISTRO="ubuntu" | |
fi | |
if [ "$DISTRO" == "debian" ] || [ "$DISTRO" == "ubuntu" ]; then | |
DISTRO_FAMILY="debian" | |
elif [ "$DISTRO" == "rhel" ] || [ "$DISTRO" == "centos" ] || [ "$DISTRO" == "ol" ] || [ "$DISTRO" == "fedora" ] || [ "$DISTRO" == "amzn" ]; then | |
DISTRO_FAMILY="fedora" | |
elif [ "$DISTRO" == "mariner" ]; then | |
DISTRO_FAMILY="mariner" | |
elif [ "$DISTRO" == "sles" ] || [ "$DISTRO" == "sle-hpc" ] || [ "$DISTRO" == "sles_sap" ]; then | |
DISTRO_FAMILY="sles" | |
else | |
script_exit "unsupported distro $DISTRO $VERSION" $ERR_UNSUPPORTED_DISTRO | |
fi | |
log_info "[>] detected: $DISTRO $VERSION $VERSION_NAME ($DISTRO_FAMILY)" | |
} | |
verify_connectivity() | |
{ | |
if [ -z "$1" ]; then | |
script_exit "Internal error. verify_connectivity require a parameter" $ERR_INTERNAL | |
fi | |
if which wget; then | |
connect_command="wget -O - --quiet --no-verbose --timeout 2 https://cdn.x.cp.wd.microsoft.com/ping --no-check-certificate" | |
elif which curl; then | |
connect_command="curl --silent --connect-timeout 2 --insecure https://cdn.x.cp.wd.microsoft.com/ping" | |
else | |
script_exit "Unable to find wget/curl commands" $ERR_INTERNAL | |
fi | |
local connected= | |
local counter=3 | |
while [ $counter -gt 0 ] | |
do | |
connected=$($connect_command) | |
if [[ "$connected" != "OK" ]]; then | |
sleep 1 | |
((counter--)) | |
else | |
counter=0 | |
fi | |
done | |
log_info "[final] connected=$connected" | |
if [[ "$connected" != "OK" ]]; then | |
script_exit "internet connectivity needed for $1" $ERR_NO_INTERNET_CONNECTIVITY | |
fi | |
log_info "[v] connected" | |
} | |
verify_channel() | |
{ | |
if [ "$CHANNEL" != "prod" ] && [ "$CHANNEL" != "insiders-fast" ] && [ "$CHANNEL" != "insiders-slow" ]; then | |
script_exit "Invalid channel: $CHANNEL. Please provide valid channel. Available channels are prod, insiders-fast, insiders-slow" $ERR_INVALID_CHANNEL | |
fi | |
} | |
verify_privileges() | |
{ | |
if [ -z "$1" ]; then | |
script_exit "Internal error. verify_privileges require a parameter" $ERR_INTERNAL | |
fi | |
if [ $(id -u) -ne 0 ]; then | |
script_exit "root privileges required to perform $1 operation" $ERR_INSUFFICIENT_PRIVILAGES | |
fi | |
} | |
verify_min_requirements() | |
{ | |
# echo "[>] verifying minimal reuirements: $MIN_CORES cores, $MIN_MEM_MB MB RAM, $MIN_DISK_SPACE_MB MB disk space" | |
local cores=$(nproc --all) | |
if [ $cores -lt $MIN_CORES ]; then | |
script_exit "MDE requires $MIN_CORES cores or more to run, found $cores." $ERR_INSUFFICIENT_REQUIREMENTS | |
fi | |
local mem_mb=$(free -m | grep Mem | awk '{print $2}') | |
if [ $mem_mb -lt $MIN_MEM_MB ]; then | |
script_exit "MDE requires at least $MIN_MEM_MB MB of RAM to run. found $mem_mb MB." $ERR_INSUFFICIENT_REQUIREMENTS | |
fi | |
local disk_space_mb=$(df -m . | tail -1 | awk '{print $4}') | |
if [ $disk_space_mb -lt $MIN_DISK_SPACE_MB ]; then | |
script_exit "MDE requires at least $MIN_DISK_SPACE_MB MB of free disk space for installation. found $disk_space_mb MB." $ERR_INSUFFICIENT_REQUIREMENTS | |
fi | |
log_info "[v] minimal requirements met" | |
} | |
find_service() | |
{ | |
if [ -z "$1" ]; then | |
script_exit "INTERNAL ERROR. find_service requires an argument" $ERR_INTERNAL | |
fi | |
lines=$(systemctl status $1 2>&1 | grep "Active: active" | wc -l) | |
if [ $lines -eq 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
verify_mdatp_installed() | |
{ | |
op=$(command -v mdatp) | |
#make sure mdatp is installed | |
if [ ! -z $op ]; then | |
#check if mdatp is onboarded or not | |
check_missing_license=$(mdatp health --field health_issues | grep "missing license" -c) | |
onboard_file=/etc/opt/microsoft/mdatp/mdatp_onboard.json | |
if ([ $check_missing_license -gt 0 ]) || ([ ! -f "$onboard_file" ]); then | |
log_info "[i] MDE already installed but not onboarded. Please use --onboard command to onboard the product." | |
else | |
mdatp_version=$($MDE_VERSION_CMD | tail -1) | |
org_id=$(mdatp health --field org_id | tail -1) | |
log_info "[i] Found MDE already installed and onboarded with org_id $org_id and app_version $mdatp_version. Either try to upgrade your MDE version using --upgrade option or Please verify that the onboarded linux server appears in Microsoft 365 Defender." | |
fi | |
else | |
script_exit "Seems like, previous version of MDE is corrupted. Please, first try to uninstall the previous version of MDE using --remove option, aborting" $ERR_CORRUPT_MDE_INSTALLED | |
fi | |
} | |
verify_conflicting_applications() | |
{ | |
# echo "[>] identifying conflicting applications (fanotify mounts)" | |
# find applications that are using fanotify | |
local conflicting_apps=$(timeout 5m find /proc/*/fdinfo/ -type f -print0 2>/dev/null | xargs -r0 grep -Fl "fanotify mnt_id" 2>/dev/null | xargs -I {} -r sh -c 'cat "$(dirname {})/../cmdline"') | |
if [ ! -z $conflicting_apps ]; then | |
if [ $conflicting_apps == "/opt/microsoft/mdatp/sbin/wdavdaemon" ]; then | |
verify_mdatp_installed | |
else | |
script_exit "found conflicting applications: [$conflicting_apps], aborting" $ERR_CONFLICTING_APPS | |
fi | |
fi | |
# find known security services | |
# | Vendor | Service | | |
# |-------------|---------------| | |
# | CrowdStrike | falcon-sensor | | |
# | CarbonBlack | cbsensor | | |
# | McAfee | MFEcma | | |
# | Trend Micro | ds_agent | | |
local conflicting_services=('ds_agent' 'falcon-sensor' 'cbsensor' 'MFEcma') | |
for t in "${conflicting_services[@]}" | |
do | |
set -- $t | |
# echo "[>] locating service: $1" | |
if find_service $1; then | |
script_exit "found conflicting service: [$1], aborting" $ERR_CONFLICTING_APPS | |
fi | |
done | |
log_info "[v] no conflicting applications found" | |
} | |
set_package_manager() | |
{ | |
if [ "$DISTRO_FAMILY" = "debian" ]; then | |
PKG_MGR=apt | |
PKG_MGR_INVOKER="apt $ASSUMEYES" | |
elif [ "$DISTRO_FAMILY" = "fedora" ]; then | |
PKG_MGR=yum | |
PKG_MGR_INVOKER="yum $ASSUMEYES" | |
elif [ "$DISTRO_FAMILY" = "mariner" ]; then | |
PKG_MGR=dnf | |
PKG_MGR_INVOKER="dnf $ASSUMEYES" | |
elif [ "$DISTRO_FAMILY" = "sles" ]; then | |
DISTRO="sles" | |
PKG_MGR="zypper" | |
PKG_MGR_INVOKER="zypper --non-interactive" | |
else | |
script_exit "unsupported distro", $ERR_UNSUPPORTED_DISTRO | |
fi | |
log_info "[v] set package manager: $PKG_MGR" | |
} | |
check_if_pkg_is_installed() | |
{ | |
if [ -z "$1" ]; then | |
script_exit "INTERNAL ERROR. check_if_pkg_is_installed requires an argument" $ERR_INTERNAL | |
fi | |
if [ "$PKG_MGR" = "apt" ]; then | |
dpkg -s $1 2> /dev/null | grep Status | grep "install ok installed" 1> /dev/null | |
else | |
rpm --quiet --query $(get_rpm_proxy_params) $1 | |
fi | |
return $? | |
} | |
get_mdatp_version() | |
{ | |
local PKG_VERSION="" | |
if [ "$DISTRO_FAMILY" = "debian" ]; then | |
PKG_VERSION=$(dpkg -s mdatp | grep -i version) | |
else | |
PKG_VERSION=$(rpm -qi mdatp | grep -i version) | |
fi | |
echo $PKG_VERSION | |
} | |
install_required_pkgs() | |
{ | |
local packages= | |
local pkgs_to_be_installed= | |
if [ -z "$1" ]; then | |
script_exit "INTERNAL ERROR. install_required_pkgs requires an argument" $ERR_INTERNAL | |
fi | |
packages=("$@") | |
for pkg in "${packages[@]}" | |
do | |
if ! check_if_pkg_is_installed $pkg; then | |
pkgs_to_be_installed="$pkgs_to_be_installed $pkg" | |
fi | |
done | |
if [ ! -z "$pkgs_to_be_installed" ]; then | |
log_info "[>] installing $pkgs_to_be_installed" | |
run_quietly "$PKG_MGR_INVOKER install $pkgs_to_be_installed" "Unable to install the required packages ($?)" $ERR_FAILED_DEPENDENCY | |
else | |
log_info "[v] required pkgs are installed" | |
fi | |
} | |
wait_for_package_manager_to_complete() | |
{ | |
local lines= | |
local counter=120 | |
while [ $counter -gt 0 ] | |
do | |
lines=$(ps axo pid,comm | grep "$PKG_MGR" | grep -v grep -c) | |
if [ "$lines" -eq 0 ]; then | |
log_info "[>] package manager freed, resuming installation" | |
return | |
fi | |
sleep 1 | |
((counter--)) | |
done | |
log_info "[!] pkg_mgr blocked" | |
} | |
install_on_debian() | |
{ | |
local packages= | |
local pkg_version= | |
local success= | |
if check_if_pkg_is_installed mdatp; then | |
pkg_version=$($MDE_VERSION_CMD) || script_exit "unable to fetch the app version. please upgrade to latest version $?" $ERR_INTERNAL | |
log_info "[i] MDE already installed ($pkg_version)" | |
return | |
fi | |
packages=(curl apt-transport-https gnupg) | |
install_required_pkgs ${packages[@]} | |
### Configure the repository ### | |
rm -f microsoft.list > /dev/null | |
run_quietly "curl -s -o microsoft.list $PMC_URL/$DISTRO/$SCALED_VERSION/$CHANNEL.list" "unable to fetch repo list" $ERR_FAILED_REPO_SETUP | |
run_quietly "mv ./microsoft.list /etc/apt/sources.list.d/microsoft-$CHANNEL.list" "unable to copy repo to location" $ERR_FAILED_REPO_SETUP | |
### Fetch the gpg key ### | |
run_quietly "curl -s https://packages.microsoft.com/keys/microsoft.asc | apt-key add -" "unable to fetch the gpg key" $ERR_FAILED_REPO_SETUP | |
run_quietly "apt-get update" "[!] unable to refresh the repos properly" | |
### Install MDE ### | |
log_info "[>] installing MDE" | |
if [ "$CHANNEL" = "prod" ]; then | |
if [[ -z "$VERSION_NAME" ]]; then | |
run_quietly "$PKG_MGR_INVOKER install mdatp" "unable to install MDE ($?)" $ERR_INSTALLATION_FAILED | |
else | |
run_quietly "$PKG_MGR_INVOKER -t $VERSION_NAME install mdatp" "unable to install MDE ($?)" $ERR_INSTALLATION_FAILED | |
fi | |
else | |
run_quietly "$PKG_MGR_INVOKER -t $CHANNEL install mdatp" "unable to install MDE ($?)" $ERR_INSTALLATION_FAILED | |
fi | |
sleep 5 | |
log_info "[v] installed" | |
} | |
install_on_mariner() | |
{ | |
local packages= | |
local pkg_version= | |
local repo= | |
local effective_distro= | |
if check_if_pkg_is_installed mdatp; then | |
pkg_version=$($MDE_VERSION_CMD) || script_exit "Unable to fetch the app version. Please upgrade to latest version $?" $ERR_INSTALLATION_FAILED | |
log_info "[i] MDE already installed ($pkg_version)" | |
return | |
fi | |
### Add Preview Repo File ### | |
tdnf -y install mariner-repos-extras-preview | |
### Install MDE ### | |
log_info "[>] installing MDE" | |
run_quietly "$PKG_MGR_INVOKER install mdatp" "unable to install MDE ($?)" $ERR_INSTALLATION_FAILED | |
sleep 5 | |
log_info "[v] installed" | |
} | |
install_on_fedora() | |
{ | |
local packages= | |
local pkg_version= | |
local repo= | |
local effective_distro= | |
if check_if_pkg_is_installed mdatp; then | |
pkg_version=$($MDE_VERSION_CMD) || script_exit "Unable to fetch the app version. Please upgrade to latest version $?" $ERR_INSTALLATION_FAILED | |
log_info "[i] MDE already installed ($pkg_version)" | |
return | |
fi | |
repo=packages-microsoft-com | |
packages=(curl yum-utils) | |
if [[ $SCALED_VERSION == 7* ]] && [ "$DISTRO" == "rhel" ]; then | |
packages=($packages deltarpm) | |
fi | |
install_required_pkgs ${packages[@]} | |
### Configure the repo name from which package should be installed | |
if [[ $SCALED_VERSION == 7* ]] && [[ "$CHANNEL" != "prod" ]]; then | |
repo=packages-microsoft-com-prod | |
fi | |
if [ "$DISTRO" == "ol" ] || [ "$DISTRO" == "fedora" ] || [ "$DISTRO" == "amzn" ]; then | |
effective_distro="rhel" | |
else | |
effective_distro="$DISTRO" | |
fi | |
### Configure the repository ### | |
run_quietly "yum-config-manager --add-repo=$PMC_URL/$effective_distro/$SCALED_VERSION/$CHANNEL.repo" "Unable to fetch the repo ($?)" $ERR_FAILED_REPO_SETUP | |
### Fetch the gpg key ### | |
run_quietly "curl https://packages.microsoft.com/keys/microsoft.asc > microsoft.asc" "unable to fetch gpg key $?" $ERR_FAILED_REPO_SETUP | |
run_quietly "rpm $(get_rpm_proxy_params) --import microsoft.asc" "unable to import gpg key" $ERR_FAILED_REPO_SETUP | |
### Install MDE ### | |
log_info "[>] installing MDE" | |
run_quietly "$PKG_MGR_INVOKER --enablerepo=$repo-$CHANNEL install mdatp" "unable to install MDE ($?)" $ERR_INSTALLATION_FAILED | |
sleep 5 | |
log_info "[v] installed" | |
} | |
install_on_sles() | |
{ | |
local packages= | |
local pkg_version= | |
local repo= | |
if check_if_pkg_is_installed mdatp; then | |
pkg_version=$($MDE_VERSION_CMD) || script_exit "unable to fetch the app version. please upgrade to latest version $?" $ERR_INTERNAL | |
log_info "[i] MDE already installed ($pkg_version)" | |
return | |
fi | |
repo=packages-microsoft-com | |
packages=(curl) | |
install_required_pkgs ${packages[@]} | |
wait_for_package_manager_to_complete | |
### Configure the repository ### | |
# add repository if it does not exist | |
lines=$($PKG_MGR_INVOKER lr | grep "packages-microsoft-com-$CHANNEL" | wc -l) | |
if [ $lines -eq 0 ]; then | |
run_quietly "$PKG_MGR_INVOKER addrepo -c -f -n microsoft-$CHANNEL https://packages.microsoft.com/config/$DISTRO/$SCALED_VERSION/$CHANNEL.repo" "unable to load repo" $ERR_FAILED_REPO_SETUP | |
fi | |
### Fetch the gpg key ### | |
run_quietly "rpm $(get_rpm_proxy_params) --import https://packages.microsoft.com/keys/microsoft.asc > microsoft.asc" "unable to fetch gpg key $?" $ERR_FAILED_REPO_SETUP | |
wait_for_package_manager_to_complete | |
### Install MDE ### | |
log_info "[>] installing MDE" | |
run_quietly "$PKG_MGR_INVOKER install $ASSUMEYES $repo-$CHANNEL:mdatp" "[!] failed to install MDE (1/2)" | |
if ! check_if_pkg_is_installed mdatp; then | |
log_warning "[r] retrying" | |
sleep 2 | |
run_quietly "$PKG_MGR_INVOKER install $ASSUMEYES mdatp" "unable to install MDE 2/2 ($?)" $ERR_INSTALLATION_FAILED | |
fi | |
sleep 5 | |
log_info "[v] installed." | |
} | |
remove_repo() | |
{ | |
# Remove mdatp if installed | |
if check_if_pkg_is_installed mdatp; then | |
remove_mdatp | |
fi | |
# Remove configured packages.microsoft.com repository | |
if [ $DISTRO == 'sles' ] || [ "$DISTRO" = "sle-hpc" ]; then | |
run_quietly "$PKG_MGR_INVOKER removerepo packages-microsoft-com-$CHANNEL" "failed to remove repo" | |
elif [ "$DISTRO_FAMILY" == "fedora" ]; then | |
local repo=packages-microsoft-com | |
if [[ $SCALED_VERSION == 7* ]] && [[ "$CHANNEL" != "prod" ]]; then | |
repo=packages-microsoft-com-prod | |
fi | |
local repo_name="$repo-$CHANNEL" | |
run_quietly "yum-config-manager --disable $repo_name" "Unable to disable the repo ($?)" $ERR_FAILED_REPO_CLEANUP | |
run_quietly "find /etc/yum.repos.d -exec grep -lqR \"\[$repo_name\]\" '{}' \; -delete" "Unable to remove repo ($?)" $ERR_FAILED_REPO_CLEANUP | |
elif [ "$DISTRO_FAMILY" == "debian" ]; then | |
if [ -f "/etc/apt/sources.list.d/microsoft-$CHANNEL.list" ]; then | |
run_quietly "rm -f '/etc/apt/sources.list.d/microsoft-$CHANNEL.list'" "unable to remove repo list ($?)" $ERR_FAILED_REPO_CLEANUP | |
fi | |
else | |
script_exit "unsupported distro for remove repo $DISTRO" $ERR_UNSUPPORTED_DISTRO | |
fi | |
log_info "[v] clean-up done." | |
} | |
upgrade_mdatp() | |
{ | |
if [ -z "$1" ]; then | |
script_exit "INTERNAL ERROR. upgrade_mdatp requires an argument (the upgrade command)" $ERR_INTERNAL | |
fi | |
if ! check_if_pkg_is_installed mdatp; then | |
script_exit "MDE package is not installed. Please install it first" $ERR_MDE_NOT_INSTALLED | |
fi | |
local VERSION_BEFORE_UPDATE=$(get_mdatp_version) | |
log_info "[>] Current $VERSION_BEFORE_UPDATE" | |
run_quietly "$PKG_MGR_INVOKER $1 mdatp" "Unable to upgrade MDE $?" $ERR_INSTALLATION_FAILED | |
local VERSION_AFTER_UPDATE=$(get_mdatp_version) | |
if [ "$VERSION_BEFORE_UPDATE" == "$VERSION_AFTER_UPDATE" ]; then | |
log_info "[>] MDE is already up to date." | |
else | |
log_info "[v] upgraded" | |
fi | |
} | |
remove_mdatp() | |
{ | |
if ! check_if_pkg_is_installed mdatp; then | |
script_exit "MDE package is not installed. Please install it first" $ERR_MDE_NOT_INSTALLED | |
fi | |
run_quietly "$PKG_MGR_INVOKER remove mdatp" "unable to remove MDE $?" $ERR_UNINSTALLATION_FAILED | |
} | |
rhel6_supported_version() | |
{ | |
local SUPPORTED_RHEL6_VERSIONS=("6.7" "6.8" "6.9" "6.10") | |
for version in ${SUPPORTED_RHEL6_VERSIONS[@]}; do | |
if [[ "$1" == "$version" ]]; then | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
scale_version_id() | |
{ | |
### We dont have pmc repos for rhel versions > 7.4. Generalizing all the 7* repos to 7 and 8* repos to 8 | |
if [ "$DISTRO_FAMILY" == "fedora" ]; then | |
if [[ $VERSION == 6* ]]; then | |
if rhel6_supported_version $VERSION; then # support versions 6.7+ | |
if [ "$DISTRO" == "centos" ] || [ "$DISTRO" == "rhel" ]; then | |
SCALED_VERSION=6 | |
else | |
script_exit "unsupported version: $DISTRO $VERSION" $ERR_UNSUPPORTED_VERSION | |
fi | |
else | |
script_exit "unsupported version: $DISTRO $VERSION" $ERR_UNSUPPORTED_VERSION | |
fi | |
elif [[ $VERSION == 7* ]] || [ "$DISTRO" == "amzn" ]; then | |
SCALED_VERSION=7 | |
elif [[ $VERSION == 8* ]] || [ "$DISTRO" == "fedora" ]; then | |
SCALED_VERSION=8 | |
elif [[ $VERSION == 9* ]]; then | |
SCALED_VERSION=9.0 | |
else | |
script_exit "unsupported version: $DISTRO $VERSION" $ERR_UNSUPPORTED_VERSION | |
fi | |
elif [ "$DISTRO_FAMILY" == "mariner" ]; then | |
if [[ $VERSION == 2* ]]; then | |
SCALED_VERSION=2 | |
else | |
script_exit "unsupported version: $DISTRO $VERSION" $ERR_UNSUPPORTED_VERSION | |
fi | |
elif [ "$DISTRO_FAMILY" == "sles" ]; then | |
if [[ $VERSION == 12* ]]; then | |
SCALED_VERSION=12 | |
elif [[ $VERSION == 15* ]]; then | |
SCALED_VERSION=15 | |
else | |
script_exit "unsupported version: $DISTRO $VERSION" $ERR_UNSUPPORTED_VERSION | |
fi | |
elif [ $DISTRO == "ubuntu" ] && [[ $VERSION != "16.04" ]] && [[ $VERSION != "18.04" ]] && [[ $VERSION != "20.04" ]] && [[ $VERSION != "22.04" ]]; then | |
SCALED_VERSION=18.04 | |
else | |
# no problems with | |
SCALED_VERSION=$VERSION | |
fi | |
log_info "[>] scaled: $SCALED_VERSION" | |
} | |
onboard_device() | |
{ | |
log_info "[>] onboarding script: $ONBOARDING_SCRIPT" | |
if ! check_if_pkg_is_installed mdatp; then | |
script_exit "MDE package is not installed. Please install it first" $ERR_MDE_NOT_INSTALLED | |
fi | |
if [ ! -f $ONBOARDING_SCRIPT ]; then | |
script_exit "error: onboarding script not found." $ERR_ONBOARDING_NOT_FOUND | |
fi | |
if [[ $ONBOARDING_SCRIPT == *.py ]]; then | |
# Make sure python is installed | |
PYTHON=$(which python || which python3) | |
if [ -z $PYTHON ]; then | |
script_exit "error: cound not locate python." $ERR_FAILED_DEPENDENCY | |
fi | |
#remove mdatp_offboard.json if present | |
mdatp_offboard_file=/etc/opt/microsoft/mdatp/mdatp_offboard.json | |
if [ -f "$mdatp_offboard_file" ]; then | |
echo "found mdatp_offboard file" | |
sudo rm -f $mdatp_offboard_file | |
if [ ! -f "$mdatp_offboard_file" ]; then | |
echo "removed mdatp_offboard file" | |
else | |
echo "failed to remove mdatp_offboard file" | |
fi | |
fi | |
# Run onboarding script | |
# echo "[>] running onboarding script..." | |
sleep 1 | |
run_quietly "$PYTHON $ONBOARDING_SCRIPT" "error: python onboarding failed" $ERR_ONBOARDING_FAILED | |
elif [[ $ONBOARDING_SCRIPT == *.sh ]]; then | |
run_quietly "sh $ONBOARDING_SCRIPT" "error: bash onboarding failed" $ERR_ONBOARDING_FAILED | |
else | |
script_exit "error: unknown onboarding script type." $ERR_ONBOARDING_FAILED | |
fi | |
# validate onboarding | |
sleep 3 | |
if [[ $(mdatp health --field org_id | grep "No license found" -c) -gt 0 ]]; then | |
script_exit "onboarding failed" $ERR_ONBOARDING_FAILED | |
fi | |
log_info "[v] onboarded" | |
} | |
offboard_device() | |
{ | |
log_info "[>] offboarding script: $OFFBOARDING_SCRIPT" | |
if ! check_if_pkg_is_installed mdatp; then | |
script_exit "MDE package is not installed. Please install it first" $ERR_MDE_NOT_INSTALLED | |
fi | |
if [ ! -f $OFFBOARDING_SCRIPT ]; then | |
script_exit "error: offboarding script not found." $ERR_OFFBOARDING_NOT_FOUND | |
fi | |
if [[ $OFFBOARDING_SCRIPT == *.py ]]; then | |
# Make sure python is installed | |
PYTHON=$(which python || which python3) | |
if [ -z $PYTHON ]; then | |
script_exit "error: cound not locate python." $ERR_FAILED_DEPENDENCY | |
fi | |
# Run offboarding script | |
# echo "[>] running offboarding script..." | |
sleep 1 | |
run_quietly "$PYTHON $OFFBOARDING_SCRIPT" "error: python offboarding failed" $ERR_OFFBOARDING_FAILED | |
elif [[ $OFFBOARDING_SCRIPT == *.sh ]]; then | |
run_quietly "sh $OFFBOARDING_SCRIPT" "error: bash offboarding failed" $ERR_OFFBOARDING_FAILED | |
else | |
script_exit "error: unknown offboarding script type." $ERR_OFFBOARDING_FAILED | |
fi | |
# validate offboarding | |
sleep 3 | |
if [[ $(mdatp health --field org_id | grep "No license found" -c) -eq 0 ]]; then | |
script_exit "offboarding failed" $ERR_OFFBOARDING_FAILED | |
fi | |
log_info "[v] offboarded" | |
} | |
set_epp_to_passive_mode() | |
{ | |
if ! check_if_pkg_is_installed mdatp; then | |
script_exit "MDE package is not installed. Please install it first" $ERR_MDE_NOT_INSTALLED | |
fi | |
if [[ $(mdatp health --field passive_mode_enabled | tail -1) == "false" ]]; then | |
log_info "[>] setting MDE/EPP to passive mode" | |
retry_quietly 3 "mdatp config passive-mode --value enabled" "failed to set MDE to passive-mode" $ERR_PARAMETER_SET_FAILED | |
else | |
log_info "[>] MDE/EPP already in passive mode" | |
fi | |
log_info "[v] passive mode set" | |
} | |
set_device_tags() | |
{ | |
for t in "${tags[@]}" | |
do | |
set -- $t | |
if [ "$1" == "GROUP" ] || [ "$1" == "SecurityWorkspaceId" ] || [ "$1" == "AzureResourceId" ] || [ "$1" == "SecurityAgentId" ]; then | |
local set_tags=$(mdatp health --field edr_device_tags) | |
local tag_exists=0 | |
local result=$(echo "$set_tags" | grep -q "\"key\":\"$1\""; echo "$?") | |
if [ $result -eq 0 ]; then | |
local value=$(echo "$set_tags" | grep -o "\"key\":\"$1\".*\"" | cut -d '"' -f 8) | |
if [ "$value" == "$2" ]; then | |
log_warning "[>] tag $1 already set to value $2." | |
tag_exists=1 | |
fi | |
fi | |
if [ $tag_exists -eq 0 ]; then | |
# echo "[>] setting tag: ($1, $2)" | |
retry_quietly 2 "mdatp edr tag set --name $1 --value $2" "failed to set tag" $ERR_PARAMETER_SET_FAILED | |
fi | |
else | |
script_exit "invalid tag name: $1. supported tags: GROUP, SecurityWorkspaceId, AzureResourceId and SecurityAgentId" $ERR_TAG_NOT_SUPPORTED | |
fi | |
done | |
log_info "[v] tags set." | |
} | |
usage() | |
{ | |
echo "mde_installer.sh v$SCRIPT_VERSION" | |
echo "usage: $1 [OPTIONS]" | |
echo "Options:" | |
echo " -c|--channel specify the channel from which you want to install. Default: insiders-fast" | |
echo " -i|--install install the product" | |
echo " -r|--remove remove the product" | |
echo " -u|--upgrade upgrade the existing product to a newer version if available" | |
echo " -o|--onboard onboard the product with <onboarding_script>" | |
echo " -f|--offboard offboard the product with <offboarding_script>" | |
echo " -p|--passive-mode set EPP to passive mode" | |
echo " -t|--tag set a tag by declaring <name> and <value>. ex: -t GROUP Coders" | |
echo " -m|--min_req enforce minimum requirements" | |
echo " -x|--skip_conflict skip conflicting application verification" | |
echo " -w|--clean remove repo from package manager for a specific channel" | |
echo " -y|--yes assume yes for all mid-process prompts (default, depracated)" | |
echo " -n|--no remove assume yes sign" | |
echo " -s|--verbose verbose output" | |
echo " -v|--version print out script version" | |
echo " -d|--debug set debug mode" | |
echo " --log-path <PATH> also log output to PATH" | |
echo " --http-proxy <URL> set http proxy" | |
echo " --https-proxy <URL> set https proxy" | |
echo " --ftp-proxy <URL> set ftp proxy" | |
echo " -h|--help display help" | |
} | |
if [ $# -eq 0 ]; then | |
usage | |
script_exit "no arguments were provided. specify --help for details" $ERR_INVALID_ARGUMENTS | |
fi | |
while [ $# -ne 0 ]; | |
do | |
case "$1" in | |
-c|--channel) | |
if [ -z "$2" ]; then | |
script_exit "$1 option requires an argument" $ERR_INVALID_ARGUMENTS | |
fi | |
CHANNEL=$2 | |
verify_channel | |
shift 2 | |
;; | |
-i|--install) | |
INSTALL_MODE="i" | |
verify_privileges "install" | |
shift 1 | |
;; | |
-u|--upgrade|--update) | |
INSTALL_MODE="u" | |
verify_privileges "upgrade" | |
shift 1 | |
;; | |
-r|--remove) | |
INSTALL_MODE="r" | |
verify_privileges "remove" | |
shift 1 | |
;; | |
-o|--onboard) | |
if [ -z "$2" ]; then | |
script_exit "$1 option requires an argument" $ERR_INVALID_ARGUMENTS | |
fi | |
ONBOARDING_SCRIPT=$2 | |
verify_privileges "onboard" | |
shift 2 | |
;; | |
-f|--offboard) | |
if [ -z "$2" ]; then | |
script_exit "$1 option requires an argument" $ERR_INVALID_ARGUMENTS | |
fi | |
OFFBOARDING_SCRIPT=$2 | |
verify_privileges "offboard" | |
shift 2 | |
;; | |
-m|--min_req) | |
MIN_REQUIREMENTS=1 | |
shift 1 | |
;; | |
-x|--skip_conflict) | |
SKIP_CONFLICTING_APPS=1 | |
shift 1 | |
;; | |
-p|--passive-mode) | |
verify_privileges "passive-mode" | |
PASSIVE_MODE=1 | |
shift 1 | |
;; | |
-t|--tag) | |
if [[ -z "$2" || -z "$3" ]]; then | |
script_exit "$1 option requires two arguments" $ERR_INVALID_ARGUMENTS | |
fi | |
verify_privileges "set-tag" | |
tags+=("$2 $3") | |
shift 3 | |
;; | |
-w|--clean) | |
INSTALL_MODE='c' | |
verify_privileges "clean" | |
shift 1 | |
;; | |
-h|--help) | |
usage "basename $0" >&2 | |
exit 0 | |
;; | |
-y|--yes) | |
ASSUMEYES=-y | |
shift 1 | |
;; | |
-n|--no) | |
ASSUMEYES= | |
shift 1 | |
;; | |
-s|--verbose) | |
VERBOSE=1 | |
shift 1 | |
;; | |
-v|--version) | |
script_exit "$SCRIPT_VERSION" $SUCCESS | |
;; | |
-d|--debug) | |
DEBUG=1 | |
shift 1 | |
;; | |
--http-proxy) | |
if [[ -z "$2" ]]; then | |
script_exit "$1 option requires two arguments" $ERR_INVALID_ARGUMENTS | |
fi | |
export http_proxy=$2 | |
shift 2 | |
;; | |
--https-proxy) | |
if [[ -z "$2" ]]; then | |
script_exit "$1 option requires two arguments" $ERR_INVALID_ARGUMENTS | |
fi | |
export https_proxy=$2 | |
shift 2 | |
;; | |
--ftp-proxy) | |
if [[ -z "$2" ]]; then | |
script_exit "$1 option requires two arguments" $ERR_INVALID_ARGUMENTS | |
fi | |
export ftp_proxy=$2 | |
shift 2 | |
;; | |
--log-path) | |
if [[ -z "$2" ]]; then | |
script_exit "$1 option requires two arguments" $ERR_INVALID_ARGUMENTS | |
fi | |
export log_path=$2 | |
shift 2 | |
;; | |
*) | |
echo "use -h or --help for details" | |
script_exit "unknown argument" $ERR_INVALID_ARGUMENTS | |
;; | |
esac | |
done | |
if [[ -z "${INSTALL_MODE}" && -z "${ONBOARDING_SCRIPT}" && -z "${OFFBOARDING_SCRIPT}" && -z "${PASSIVE_MODE}" && ${#tags[@]} -eq 0 ]]; then | |
script_exit "no installation mode specified. Specify --help for help" $ERR_INVALID_ARGUMENTS | |
fi | |
# echo "--- mde_installer.sh v$SCRIPT_VERSION ---" | |
log_info "--- mde_installer.sh v$SCRIPT_VERSION ---" | |
### Validate mininum requirements ### | |
if [ $MIN_REQUIREMENTS ]; then | |
verify_min_requirements | |
fi | |
## Detect the architecture type | |
detect_arch | |
### Detect the distro and version number ### | |
detect_distro | |
### Scale the version number according to repos avaiable on pmc ### | |
scale_version_id | |
### Set package manager ### | |
set_package_manager | |
### Act according to arguments ### | |
if [ "$INSTALL_MODE" == "i" ]; then | |
verify_connectivity "package installation" | |
if [ -z $SKIP_CONFLICTING_APPS ]; then | |
verify_conflicting_applications | |
fi | |
if [ "$DISTRO_FAMILY" == "debian" ]; then | |
install_on_debian | |
elif [ "$DISTRO_FAMILY" == "fedora" ]; then | |
install_on_fedora | |
elif [ "$DISTRO_FAMILY" == "mariner" ]; then | |
install_on_mariner | |
elif [ "$DISTRO_FAMILY" = "sles" ]; then | |
install_on_sles | |
else | |
script_exit "unsupported distro $DISTRO $VERSION" $ERR_UNSUPPORTED_DISTRO | |
fi | |
elif [ "$INSTALL_MODE" == "u" ]; then | |
verify_connectivity "package update" | |
if [ "$DISTRO_FAMILY" == "debian" ]; then | |
upgrade_mdatp "$ASSUMEYES install --only-upgrade" | |
elif [ "$DISTRO_FAMILY" == "fedora" ]; then | |
upgrade_mdatp "$ASSUMEYES update" | |
elif [ "$DISTRO_FAMILY" == "mariner" ]; then | |
upgrade_mdatp "$ASSUMEYES update" | |
elif [ "$DISTRO_FAMILY" == "sles" ]; then | |
upgrade_mdatp "up $ASSUMEYES" | |
else | |
script_exit "unsupported distro $DISTRO $VERSION" $ERR_UNSUPPORTED_DISTRO | |
fi | |
elif [ "$INSTALL_MODE" = "r" ]; then | |
if remove_mdatp; then | |
script_exit "[v] removed MDE" $SUCCESS | |
fi | |
elif [ "$INSTALL_MODE" == "c" ]; then | |
if remove_repo; then | |
script_exit "[v] removed repo" $SUCCESS | |
fi | |
fi | |
if [ ! -z $PASSIVE_MODE ]; then | |
set_epp_to_passive_mode | |
fi | |
if [ ! -z $ONBOARDING_SCRIPT ]; then | |
onboard_device | |
fi | |
if [ ! -z $OFFBOARDING_SCRIPT ]; then | |
offboard_device | |
fi | |
if [ ${#tags[@]} -gt 0 ]; then | |
set_device_tags | |
fi | |
script_exit "--- mde_installer.sh ended. ---" $SUCCESS |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment