Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 186 additions & 22 deletions Solutions/SAP/sapcon-sentinel-kickstart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,82 @@ function verlt() {
[ "$1" = "$2" ] && return 1 || verlte $1 $2
}

function validate_guid() {
local input="$1"
local name="$2"

if [ -z "$input" ]; then
return 0
fi

if ! [[ "$input" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
echo "Invalid $name: must be a valid GUID format (e.g., 12345678-1234-1234-1234-123456789012)"
exit 1
fi
}

function validate_port() {
local input="$1"
local name="$2"

if [ -z "$input" ]; then
return 0
fi

if ! [[ "$input" =~ ^[0-9]+$ ]] || [ "$input" -lt 1 ] || [ "$input" -gt 65535 ]; then
echo "Invalid $name: must be a valid port number (1-65535)"
exit 1
fi
}

function validate_sap_systemnr() {
local input="$1"
local name="$2"

if [ -z "$input" ]; then
return 0
fi

if ! [[ "$input" =~ ^[0-9]{2}$ ]]; then
echo "Invalid $name: must be exactly 2 digits (00-99)"
exit 1
fi
}

function validate_sap_client() {
local input="$1"
local name="$2"

if [ -z "$input" ]; then
return 0
fi

if ! [[ "$input" =~ ^[0-9]{3}$ ]]; then
echo "Invalid $name: must be exactly 3 digits (000-999)"
exit 1
fi
}

function validate_sap_sid() {
local input="$1"
local name="$2"

if [ -z "$input" ]; then
return 0
fi

# Convert to uppercase
input=$(echo "$input" | tr '[:lower:]' '[:upper:]')

if ! [[ "$input" =~ ^[A-Z][A-Z0-9]{2}$ ]]; then
echo "Invalid $name: must be 3 characters (first letter A-Z, remaining alphanumeric)"
exit 1
fi

# Update the variable in the caller's scope using indirect assignment
printf -v "$2" '%s' "$input"
}

function read_password() {
varname="$1"
while [ -z "${!varname}" ]; do
Expand Down Expand Up @@ -40,7 +116,7 @@ function read_password() {
done
stty echo
echo ""
eval "$1"='$PASSWORD'
printf -v "$1" '%s' "$PASSWORD"
if [ -z "${!varname}" ]; then
echo "Invalid value supplied for $1. Value cannot be empty. Please try again" >$(tty)
fi
Expand All @@ -54,7 +130,7 @@ function read_value() {
if [ -z "$varvalue" ]; then
echo "Supplied value for $2 is empty. Please try again." >$(tty)
fi
eval "$1"='$varvalue'
printf -v "$1" '%s' "$varvalue"
done
}

Expand Down Expand Up @@ -89,6 +165,7 @@ function install_package() {
MODE="kvmi"
CONNECTIONMODE="abap"
CONFIGPATH="/opt"
CUSTOM_CONFIGPATH=""
TRUSTEDCA=()
CLOUD='public'
UPDATEPOLICY='{ "auto_update" : true }'
Expand All @@ -100,7 +177,9 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
--configpath)
CONFIGPATH="$2"
CUSTOM_CONFIGPATH="$2"
# For security, always use /opt. Custom path migration shown at end.
CONFIGPATH="/opt"
shift 2
;;
--connectionmode)
Expand All @@ -113,10 +192,12 @@ while [[ $# -gt 0 ]]; do
;;
--systemnr)
SYSTEMNR="$2"
validate_sap_systemnr "$SYSTEMNR" "System Number (--systemnr)"
shift 2
;;
--sid)
SID="$2"
validate_sap_sid "$SID" "SID"
shift 2
;;
--hostnetwork)
Expand All @@ -125,6 +206,7 @@ while [[ $# -gt 0 ]]; do
;;
--clientnumber)
CLIENTNUMBER="$2"
validate_sap_client "$CLIENTNUMBER" "Client Number (--clientnumber)"
shift 2
;;
--messageserverhost)
Expand All @@ -133,6 +215,7 @@ while [[ $# -gt 0 ]]; do
;;
--messageserverport)
MESSAGESERVERPORT="$2"
validate_port "$MESSAGESERVERPORT" "Message Server Port (--messageserverport)"
shift 2
;;
--logongroup)
Expand All @@ -154,6 +237,7 @@ while [[ $# -gt 0 ]]; do
;;
--appid)
APPID="$2"
validate_guid "$APPID" "Application ID (--appid)"
shift 2
;;
--appsecret)
Expand All @@ -162,6 +246,7 @@ while [[ $# -gt 0 ]]; do
;;
--tenantid)
TENANT="$2"
validate_guid "$TENANT" "Tenant ID (--tenantid)"
shift 2
;;
--kvaultname)
Expand All @@ -170,6 +255,7 @@ while [[ $# -gt 0 ]]; do
;;
--loganalyticswsid)
logwsid="$2"
validate_guid "$logwsid" "Log Analytics Workspace ID (--loganalyticswsid)"
shift 2
;;
--loganalyticskey)
Expand Down Expand Up @@ -642,6 +728,8 @@ if [ -z "$SID" ]; then
echo 'Invalid SID, SID length should be 3'
read -r -p 'SID: ' SID
done
SID=$(echo "$SID" | tr '[:lower:]' '[:upper:]')
validate_sap_sid "$SID" "SID"
fi

if [ -z "$CLIENTNUMBER" ]; then
Expand All @@ -650,6 +738,7 @@ if [ -z "$CLIENTNUMBER" ]; then
echo 'Invalid Client number, Client number length should be 3'
read -r -p 'Client: ' CLIENTNUMBER
done
validate_sap_client "$CLIENTNUMBER" "Client Number"
fi

if [ "$CONNECTIONMODE" == 'abap' ] && [ ! $USESNC ]; then
Expand Down Expand Up @@ -703,8 +792,8 @@ if [ $USESNC ]; then

if [ -f "$CLIENTPFX" ]; then
#PFX file exists. unpack it
openssl pkcs12 -in "$CLIENTPFX" -out "$sysfileloc"sec/client.p12 -nodes -passin pass:$CLIENTPFXPWD
MYCERT=$(openssl pkcs12 -info -in "$CLIENTPFX" -nodes -nokeys -clcerts -passin pass:$CLIENTPFXPWD | awk '/-----BEGIN/{a=1}/-----END/{print;a=0}a' | tr -d '\r\n' | sed -E 's/-+BEGIN CERTIFICATE-+//' | sed -E 's/-+END CERTIFICATE-+//' | sed 's/\//\\\//g')
openssl pkcs12 -in "$CLIENTPFX" -out "$sysfileloc"sec/client.p12 -nodes -passin "pass:$CLIENTPFXPWD"
MYCERT=$(openssl pkcs12 -info -in "$CLIENTPFX" -nodes -nokeys -clcerts -passin "pass:$CLIENTPFXPWD" | awk '/-----BEGIN/{a=1}/-----END/{print;a=0}a' | tr -d '\r\n' | sed -E 's/-+BEGIN CERTIFICATE-+//' | sed -E 's/-+END CERTIFICATE-+//' | sed 's/\//\\\//g')
fi

if [ ! -f "$CLIENTPFX" ]; then
Expand Down Expand Up @@ -826,34 +915,67 @@ if [ $? -eq 0 ]; then
sudo docker stop "$containername" >/dev/null
sudo docker container rm "$containername" >/dev/null
fi
sncline=""
if [ $USESNC ]; then
sncline="-e SECUDIR=/sapcon-app/sapcon/config/system/sec/"
fi

if [ -n "$HTTPPROXY" ]; then
httpproxyline="-e HTTP_PROXY=$HTTPPROXY -e HTTPS_PROXY=$HTTPPROXY -e NO_PROXY=169.254.169.254"
fi
cmdparams=" --label Cloud=$CLOUD"
# Generating SENTINEL_AGENT_GUID
cmdparams+=" -e SENTINEL_AGENT_GUID=$(uuidgen) "

if [ $HOSTNETWORK ]; then
cmdparams+=" --network host "
fi
# Build base container parameters (safe, validated values only)
cmdparams=("--label" "Cloud=$CLOUD" "-e" "SENTINEL_AGENT_GUID=$(uuidgen)")

if [ "$MODE" == "kvmi" ]; then
echo "Creating docker container for use with Azure Key vault and managed VM identity"
sudo docker create -v "$sysfileloc":/sapcon-app/sapcon/config/system $cmdparams $sncline $httpproxyline --name "$containername" $dockerimage$tagver >/dev/null
sudo docker create -v "$sysfileloc":/sapcon-app/sapcon/config/system "${cmdparams[@]}" --name "$containername" $dockerimage$tagver >/dev/null
elif [ "$MODE" == "kvsi" ]; then
echo "Creating docker container for use with Azure Key vault and application authentication"
sudo docker create -v "$sysfileloc":/sapcon-app/sapcon/config/system $cmdparams $sncline $httpproxyline -e AZURE_CLIENT_ID="$APPID" -e AZURE_CLIENT_SECRET="$APPSECRET" -e AZURE_TENANT_ID="$TENANT" --name "$containername" $dockerimage$tagver >/dev/null
sudo docker create -v "$sysfileloc":/sapcon-app/sapcon/config/system "${cmdparams[@]}" -e AZURE_CLIENT_ID="$APPID" -e AZURE_CLIENT_SECRET="$APPSECRET" -e AZURE_TENANT_ID="$TENANT" --name "$containername" $dockerimage$tagver >/dev/null
elif [ "$MODE" == "cfgf" ]; then
echo "Creating docker container for use with secrets in config file"
sudo docker create -v "$sysfileloc":/sapcon-app/sapcon/config/system $cmdparams $sncline $httpproxyline --name "$containername" $dockerimage$tagver >/dev/null
sudo docker create -v "$sysfileloc":/sapcon-app/sapcon/config/system "${cmdparams[@]}" --name "$containername" $dockerimage$tagver >/dev/null
fi
echo 'Azure Sentinel SAP connector was updated for instance '"$intprefix"

# Show additional configuration instructions if user specified advanced options
if [ $USESNC ] || [ -n "$HTTPPROXY" ] || [ $HOSTNETWORK ]; then
echo ""
echo "═══════════════════════════════════════════════════════════════════════════"
echo "ADDITIONAL CONFIGURATION REQUIRED"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
echo "You specified advanced options that require manual configuration."
echo "Run the following commands to apply them:"
echo ""
echo " sudo docker stop \"$containername\""
echo " sudo docker rm \"$containername\""
echo -n " sudo docker create -v \"$sysfileloc\":/sapcon-app/sapcon/config/system"
echo -n " --label Cloud=$CLOUD"
echo -n " -e SENTINEL_AGENT_GUID=$(sudo docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$containername" 2>/dev/null | grep SENTINEL_AGENT_GUID | cut -d= -f2)"

if [ $USESNC ]; then
echo -n " -e SECUDIR=/sapcon-app/sapcon/config/system/sec/"
fi

if [ -n "$HTTPPROXY" ]; then
echo -n " -e HTTP_PROXY='$HTTPPROXY'"
echo -n " -e HTTPS_PROXY='$HTTPPROXY'"
echo -n " -e NO_PROXY=169.254.169.254"
fi

if [ $HOSTNETWORK ]; then
echo -n " --network host"
fi

if [ "$MODE" == "kvsi" ]; then
echo -n " -e AZURE_CLIENT_ID=\"$APPID\""
echo -n " -e AZURE_CLIENT_SECRET=\"$APPSECRET\""
echo -n " -e AZURE_TENANT_ID=\"$TENANT\""
fi

echo -n " --name \"$containername\""
echo " $dockerimage$tagver"
echo " sudo docker start \"$containername\""
echo ""
echo "Review the command above before executing. Verify all values are correct."
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
fi

sudo docker cp "$containername":/sapcon-app/template/systemconfig-kickstart-blank.json "$sysfileloc$sysconf"
if [ ! $? -eq 0 ]; then
echo 'Error accessing the local folder.'
Expand Down Expand Up @@ -883,6 +1005,48 @@ jq --arg sysnr "$SYSTEMNR" '.abap_central_instance += {"sysnr": $sysnr}' "$sysfi
jq --arg client "$CLIENTNUMBER" '.abap_central_instance += {"client": $client}' "$sysfileloc$sysconf" > "$sysfileloc$sysconf.tmp" && mv "$sysfileloc$sysconf.tmp" "$sysfileloc$sysconf"
jq --arg sysid "$SID" '.abap_central_instance += {"sysid": $sysid}' "$sysfileloc$sysconf" > "$sysfileloc$sysconf.tmp" && mv "$sysfileloc$sysconf.tmp" "$sysfileloc$sysconf"

# Show CONFIGPATH migration instructions if custom path was specified
if [ -n "$CUSTOM_CONFIGPATH" ]; then
CUSTOM_SYSFILELOC="$CUSTOM_CONFIGPATH/$containername/$intprefix/"
ACTUAL_GUID=$(sudo docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$containername" 2>/dev/null | grep SENTINEL_AGENT_GUID | cut -d= -f2)
echo ""
echo "═══════════════════════════════════════════════════════════════════════════"
echo "CUSTOM CONFIGURATION PATH SPECIFIED"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
echo "You specified: --configpath $CUSTOM_CONFIGPATH"
echo "Configuration files were created in: $sysfileloc"
echo ""
echo "To migrate to your custom path, run these commands:"
echo ""
echo " # 1. Stop and remove the container"
echo " sudo docker stop \"$containername\""
echo " sudo docker rm \"$containername\""
echo ""
echo " # 2. Move configuration files to custom location"
echo " sudo mkdir -p \"$CUSTOM_SYSFILELOC\""
echo " sudo mv \"$sysfileloc\"* \"$CUSTOM_SYSFILELOC\""
echo " sudo rmdir \"$sysfileloc\""
echo ""
echo " # 3. Recreate container with new mount path"
echo -n " sudo docker create -v \"$CUSTOM_SYSFILELOC\":/sapcon-app/sapcon/config/system"
echo -n " --label Cloud=$CLOUD"
echo -n " -e SENTINEL_AGENT_GUID=$ACTUAL_GUID"
if [ "$MODE" == "kvsi" ]; then
echo -n " -e AZURE_CLIENT_ID=\"$APPID\""
echo -n " -e AZURE_CLIENT_SECRET=\"$APPSECRET\""
echo -n " -e AZURE_TENANT_ID=\"$TENANT\""
fi
echo -n " --name \"$containername\""
echo " $dockerimage$tagver"
echo ""
echo " # 4. Start the container"
echo " sudo docker start \"$containername\""
echo ""
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
fi

if [ "$MODE" == 'kvmi' ] || [ "$MODE" == 'kvsi' ]; then
jq '.secrets_source += {"secrets": "AZURE_KEY_VAULT"}' "$sysfileloc$sysconf" > "$sysfileloc$sysconf.tmp" && mv "$sysfileloc$sysconf.tmp" "$sysfileloc$sysconf"
jq --arg intprefix "$intprefix" '.secrets_source += {"intprefix": $intprefix}' "$sysfileloc$sysconf" > "$sysfileloc$sysconf.tmp" && mv "$sysfileloc$sysconf.tmp" "$sysfileloc$sysconf"
Expand Down
Loading
Loading