#!/bin/bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)prepare-tenancy 3.2.0 13.09.2025 (c)2025 Standby.cloud # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ # #@ Run this script in CloudShell to provide a basic set of tooling and useful resources. #@ #@Usage: prepare-tenancy [action] #@ Action: optional #@ remove: Removes most of the deployed resources. # # Exit codes: # 01: Unsupported platform. # 02: No 'curl', 'oci' or 'jq' in PATH. # 03: No administrator privileges (needed to proceed). # 04: No internet connection. # 05: Unknown condition. # # Supported platforms: # # Only works in OCI CloudShell (tested with OL 8) # # Usage: # # # Deploy resources # curl -skL https://standby.cloud/download/extra/prepare-tenancy | bash # # # Destroy resources # curl -skL https://standby.cloud/download/extra/prepare-tenancy | bash -s remove # # Infos: # OCI_CLI_CLOUD_SHELL: "True" if CloudShell is in use (set by CloudShell) # OCI_TENANCY: OCID of the tenancy (set by CloudShell) # OCI_TENANCY_NAME: Name of the tenancy # OCI_REGION: Region where CloudShell is started in (set by CloudShell) # OCI_HOME_REGION_KEY: e.g. FRA # OCI_HOME_REGION: e.g. eu-frankfurt-1 # OCI_CS_USER_OCID: OCID of CloudShell user (set by CloudShell) # OCI_CS_USER_NAME: Name of CloudShell user # OCI_CLOUD_ADMIN_NAME: Name of technical user "CloudAdmin" if configuration variable "CloudAdmin" is set, otherwise name of ClouShell user # OCI_CLOUD_ADMIN_OCID: OCID of technical user "CloudAdmin" if configuration variable "CloudAdmin" is set, otherwise OCID of ClouShell user # OCI_ADMINISTRATORS_OCID: OCID of group "Administrators" in Default domain # OCI_NAMESPACE: e.g. frelkczkpnqg # # Update history: # # V 3.2.0 13.09.2025 New version # # Set configuration variables RotateLog="false" # Set variable to true to overwrite logfiles each time script is started otherwise set variable to false EnableCloudGuard="true" # Set variable to true to enable Cloud Guard CreateDrg="true" # Set variable to true to create a DRG in the Home Region Label="Basic Landing Zone" # Name of our Landing Zone for tagging and cost tracking CloudAdmin="CloudAdmin" # Name of technical user with administrator rights. Leave empty to use CloudShellUser instead. OpsName="Operations" # Name of the Operations compartment - Cannot be empty but can be renamed to anything useful NetwName="Network" # Name of the Network compartment - Leave empty if not needed AppName="Application" # Name of the Operations compartment - Leave empty if not needed DbName="Database" # Name of the Operations compartment - Leave empty if not needed Images="OL9Base.oci OL9Bastion.oci" # Leave empty if you don't need master images # Repository where we load our scripts from readonly storageurl="https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/frcc4jd4wdkp/b/download/o" # Set some defaults exitcode=0 param=`echo "$1" | tr '[:upper:]' '[:lower:]'` progstr="prepare-tenancy" scriptsfile="install-scripts" toolsfile="install-tools" logdir="${HOME}/logs" logfile="${logdir}/${progstr}.log" tcfgfile="${HOME}/.admintools" ocifolder="${HOME}/.oci" ocijson="${ocifolder}/config.json" projectjson="${ocifolder}/project.json" blzjson="${ocifolder}/blz.json" imp_apikey="${ocifolder}/oci_api_key.pem" # Key to import imp_tenancykey="${HOME}/.ssh/id_rsa" # Key to import jumphostname="jumphost" imp_jumphostkey="${HOME}/.ssh/${jumphostname}_id_rsa" # Key to import scratch="/tmp/scratch.tmp" poltsv="/tmp/policies.tsv" keysfile="/tmp/keys.tsv" tagsfile="/tmp/tags.json" labelfile="/tmp/label.json" usersfile="/tmp/users.json" groupsfile="/tmp/groups.json" policiesfile="/tmp/policies.json" result="" # Environment variables export ENVELOPE_TABLE=false # Suppress "content" in print-table export LOGFILE="$logfile" # Write everything to this logfile # Define useful aliases shopt -s expand_aliases alias stripcomment="sed 's|#.*$||'" # Ignore all comments alias tolower="tr '[:upper:]' '[:lower:]'" # Lowercase string alias toupper="tr '[:lower:]' '[:upper:]'" # Uppercase string alias ll="ls -als" # Full directory listing alias more="less -R" # Show color codes alias tailfromline2="tail -n +2" # Ignore first line alias taillastline="tail -n 1" # Only lis last line # Get current user and os ME=`whoami` # Current user OS=`uname -s` # Infos about the host os (e.g. Darwin, SunOS, Linux) # Set PATH PATH="/bin:/.local/bin:/usr/local/bin:$PATH" # Check, if tools are available and locate them function CheckTools { curl=`which "curl" 2>/dev/null | sed 's|^no curl in .*||'` jq=`which "jq" 2>/dev/null | sed 's|^no jq in .*||'` oci=`which "oci" 2>/dev/null | sed 's|^no oci in .*||'` rclone=`which "rclone" 2>/dev/null | sed 's|^no rclone in .*||'` setuptools=`which "setup-tools" 2>/dev/null | sed 's|^no setup-tools in .*||'` } # Write string to log with current date and time function WriteLog { local param=${1} if [ "$logfile" != "" ]; then curdate=`date "+%Y-%m-%d %T"` printf "%s\t%s\n" "$curdate" "$param" >> "$logfile" fi } # Delete file function DeleteFile { local filename=${1} if [ "$filename" != "" ]; then if [ -f "$filename" ]; then rm -f "$filename" fi fi } # Delete tempfiles function Cleanup { DeleteFile "/tmp/$scriptsfile" DeleteFile "/tmp/$toolsfile" DeleteFile "${scratch}.json" DeleteFile "${scratch}.txt" DeleteFile "${scratch}.pre" DeleteFile "${scratch}.err" DeleteFile "$scratch" DeleteFile "$poltsv" DeleteFile "$keysfile" DeleteFile "$tagsfile" DeleteFile "$labelfile" DeleteFile "$usersfile" DeleteFile "$groupsfile" DeleteFile "$policiesfile" # Reset terminal tset } # Download file from standby.cloud function DownloadFile { local filename=${1} local beta=${2} local stage="latest" local grepres="" local code="" local stat=1 if [ "$filename" != "" ]; then if [ "$beta" = "true" ]; then stage="beta" fi $curl -skL "${storageurl}/${stage}/$filename" -o "/tmp/$filename" stat=$? if [ $stat -eq 0 -a -r "/tmp/$filename" ]; then grepres=`grep '^{"code":"' "/tmp/$filename"` if [ "$grepres" != "" ]; then code=`echo "$grepres" | cut -d'"' -f4` echo "Errorcode: '$code'" stat=1 else chmod 755 "/tmp/$filename" fi fi fi return $stat } # Check for internet connection function CheckInternet { local i=0 local max=10 local stat=1 while [ $stat -ne 0 -a $i -lt $max ]; do DownloadFile "$scriptsfile" "false" stat=$? # Increase counter let "i++" if [ $stat -ne 0 -a $i -lt $max ]; then # Pause 3 seconds sleep 3 fi done # Exit, if we can't download file if [ $stat -ne 0 ]; then exitcode=4 echo "No internet connection. Error: '$stat'." Cleanup exit 4 fi } # Check if CloudShell User is in the Administrators group function CheckForAdminUser { local userid=${1} local result="" local i=0 local max=10 local stat=1 if [ "$oci" != "" ]; then while [ $stat -ne 0 -a $i -lt $max ]; do # We have to use oci gere, beacause tools are not installed yet (missing norm-json) $oci iam user list-groups --user-id "$userid" > $scratch 2>/dev/null stat=$? # Increase counter let "i++" if [ $stat -eq 0 -a -s $scratch ]; then result=`$jq -r '.data[] | select(.name=="Administrators") | .id' $scratch` if [ "$result" != "" ]; then result=`echo "$result" | grep "^ocid"` fi else if [ $i -lt $max ]; then # Pause 3 seconds sleep 3 fi fi done fi if [ "$result" != "" ]; then OCI_ADMINISTRATORS_OCID="$result" else stat=1 fi return $stat } # Call oci cli and try several times if it fails (maybe because of connection errors) function CallOCI { local max=30 local i=0 local stat=1 if [ "$oci" != "" ]; then while [ $stat -ne 0 -a $i -lt $max ]; do $oci "$@" >${scratch}.pre 2>${scratch}.err stat=$? # Increase counter let "i++" if [ $stat -eq 0 ]; then if [ -s ${scratch}.pre ]; then norm-json --quiet --import ${scratch}.pre stat=$? # norm-json failed - normally this should never be the case if [ $stat -ne 0 ]; then cat ${scratch}.pre stat=$? fi else cat ${scratch}.pre stat=$? fi else if [ $i -lt $max ]; then # Command was not successful. Wait for a few seconds until we try again sleep 3 fi fi done DeleteFile ${scratch}.pre fi return $stat } # Check for internet connection function GetNamespace { local result="" local stat=0 CallOCI os ns get > $scratch stat=$? if [ $stat -eq 0 -a -s $scratch ]; then result=`$jq -r '.[]' $scratch` fi if [ "$result" != "" ]; then OCI_NAMESPACE="$result" else exitcode=4 echo "Unable to get namespace - stat: '$stat'." Cleanup exit 4 fi } function ModifyJson { local jsonid=${1} local filename=${2} local max=0 local i=0 if [ "$jsonid" != "" -a "$filename" != "" ]; then printf '' > $scratch if [ -r "$blzjson" ]; then # BLZ JSON already exists - copy out each existing record, except the record specified with jsonid max=`$jq '. | length' "$blzjson"` while [ $i -lt $max ]; do result=`$jq '.['$i'].'$jsonid "$blzjson"` if [ "$result" = "null" ]; then $jq '.['$i']' "$blzjson" >> $scratch fi # Increase counter let "i++" done fi if [ -r "$filename" ]; then # Import tsv file printf '{\n"%s": ' "$jsonid" >> $scratch print-table --import "$filename" --output json >> $scratch printf '}\n' >> $scratch fi # Create BLZ JSON touch $blzjson chmod 600 $blzjson cat $scratch | $jq --slurp > "$blzjson" fi } # Create a user (if he doesn't exist) and assign him to 'Administrators' goup. Keep email empty for a technical user function CreateAdminUser { local username=${1} local email=${2} local stat=0 local userid="" local userdesc="" local userocid="" local emailopt="" if [ "$username" != "" ]; then if [ "$email" = "" ]; then userid="tusr" userdesc="Technical user" else userid="aúsr" userdesc="Admin user" emailopt="--email $email" fi userocid=`cat $usersfile | $jq -r '.[] | select(.name=="'$username'") | .id'` if [ "$userocid" = "" ]; then # User does not exist yet CallOCI iam user create --name "$username" $emailopt --description "$userdesc for Cloud Administration" \ --defined-tags file://$labelfile --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 ]; then userocid=`cat $scratch | $jq -r '.[].id'` if [ "$userocid" != "" ]; then if [ "$username" = "$CloudAdmin" ]; then OCI_CLOUD_ADMIN_OCID="$userocid" OCI_CLOUD_ADMIN_NAME="$CloudAdmin" fi echo "$userdesc '$username' created." CallOCI iam group add-user --user-id "$userocid" --group-id "$OCI_ADMINISTRATORS_OCID" > /dev/null stat=$? if [ $stat -eq 0 ]; then echo "$userdesc '$username' assigned to group 'Administrators'." else echo "Unable to assign '$username' to group 'Administrators'. Error: '$stat'." fi fi fi else if [ "$username" = "$CloudAdmin" ]; then OCI_CLOUD_ADMIN_OCID="$userocid" OCI_CLOUD_ADMIN_NAME="$CloudAdmin" fi # User already exisits - check if he is assigned to administratos group echo "$userdesc '$username' already exisits." CheckForAdminUser "$userocid" stat=$? if [ $stat -ne 0 ]; then CallOCI iam group add-user --user-id "$userocid" --group-id "$OCI_ADMINISTRATORS_OCID" > /dev/null stat=$? if [ $stat -eq 0 ]; then echo "$userdesc '$username' assigned to group 'Administrators'." else echo "Unable to assign '$username' to group 'Administrators'. Error: '$stat'." fi fi fi # Write admin user to tsv file printf '%s\t%s\t%s\n' "$userid" "$userocid" "$username" >> ${scratch}.txt fi } # Create all needed users for administration function CreateAllAdminUsers { # Get User Name from User OCID OCI_CS_USER_NAME=`jq -r '.[] | select(.id=="'$OCI_CS_USER_OCID'") | .name' "$usersfile"` # Create tsv file with admin users printf '%s\t%s\t%s\n' "id" "ocid" "name" > ${scratch}.txt printf '%s\t%s\t%s\n' "csusr" "$OCI_CS_USER_OCID" "$OCI_CS_USER_NAME" >> ${scratch}.txt # If no technical user is specified in configuration variables - use CloudShell user if [ "$CloudAdmin" = "" ]; then OCI_CLOUD_ADMIN_OCID="$OCI_CS_USER_OCID" OCI_CLOUD_ADMIN_NAME="$OCI_CS_USER_NAME" else # Create each single admin user CreateAdminUser "$CloudAdmin" fi # Extend the blz json result=`filecheck -sl ${scratch}.txt` if [ "$result" != "" ]; then ModifyJson "users" ${scratch}.txt fi } # Create all keys for user (if they don't exist) function CreateKeys { local username=${1} local import=${2} if [ "$username" != "" -a -r "$keysfile" ]; then result=`grep "^$username " "$keysfile"` if [ "$result" = "" ]; then # Key doesn't exist yet if [ "$import" != "" -a -r "$import" ]; then # Found key to import key-management import "$import" --username "$username" else key-management create --username "$username" fi else echo "Key '$username' already exists." fi fi } # Copy images to our objectstorage and create cutome images function CreateCustomImages { local writeuri="" local imagename="" local imageid="" local imageocid="" local compname="" local compid="" local bucket="" local image="" local max=20 local i=0 local response=0 local stat=0 if [ -r "$projectjson" -a "$Images" != "" ]; then # Copy images compname=`$jq -r '.project.name' "$projectjson"` compid=`$jq -r '.project.compartmentid' "$projectjson"` writeuri=`$jq -r '.project.writeuri' "$projectjson"` if [ "$writeuri" != "" ]; then printf '%s\t%s\t%s\n' "id" "ocid" "name" > ${scratch}.txt writeuri=`dirname "$writeuri"` bucket=`basename "$writeuri"` for image in $Images; do # Get image type / id imagename=`echo "${image%.*}"` imageid="unknown" result=`echo "$image" | grep -i "base"` if [ "$result" != "" ]; then imageid="base" else result=`echo "$image" | grep -i "bastion"` if [ "$result" != "" ]; then imageid="bastion" fi fi # Download image response=`$curl -o /dev/null --silent -Iw '%{http_code}' "${writeuri}/o/images/$image"` if [ $response -ne 200 ]; then echo "Downloading image '$image' to bucket '$bucket'." stat=1 i=0 while [ $stat -ne 0 -a $i -lt $max ]; do store-object --quiet --force --prefix "images" --image "$image" --url "${writeuri}/o" >>$logfile 2>&1 stat=$? # Increase counter let "i++" if [ $stat -ne 0 -a $i -lt $max ]; then # Command was not successful. Wait for a few seconds until we try again sleep 3 fi done if [ $stat -ne 0 ]; then echo "Error while downloading image '$image'. Error: '$stat'" fi else echo "Image '$image' already downloaded." fi # Create image if [ $stat -eq 0 -a "$compid" != "" ]; then imageocid="`$jq -r '.[] | select(.images) | .images[] | select(.id=="'$imageid'") | .ocid' "$blzjson"`" if [ "$imageocid" != "" ]; then echo "Custom image '$imagename' already created." else echo "Creating custom image '$imagename' in compartment '$compname'." CallOCI compute image import from-object-uri --uri "${writeuri}/o/images/$image" --compartment-id "$compid" \ --display-name "$imagename" --region "$OCI_HOME_REGION" --defined-tags file://$labelfile >$scratch stat=$? if [ $stat -eq 0 ]; then imageocid=`$jq -r '.[].id' $scratch` fi fi # Check if we found a valid ocid and add to blz json if [ "$imageocid" != "" ]; then imageocid=`echo "$imageocid" | grep "^ocid"` fi if [ "$imageocid" != "" ]; then printf '%s\t%s\t%s\n' "$imageid" "$imageocid" "$imagename" >> ${scratch}.txt fi fi done # Extend the blz json result=`filecheck -sl ${scratch}.txt` if [ "$result" != "" ]; then ModifyJson "images" ${scratch}.txt fi fi fi } function GetGroup { local groupid=${1} local output=${2} local groupname="" local groupdesc="" local groupocid="" local stat=0 case "$groupid" in csu) # Cloud Shell Users groupname="CloudShellUsers" groupdesc="Allow users to use CloudShell" printf '[\n' > ${scratch}.json printf ' "allow group %s to use cloud-shell in tenancy",\n' "$groupname" >> ${scratch}.json printf ' "allow group %s to use cloud-shell-public-network in tenancy"\n' "$groupname" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; rau) # Read Access Users groupname="ReadAccessUsers" groupdesc="Allow users to read all objects in Tenancy" printf '[\n' > ${scratch}.json printf ' "allow group %s to read all-resources in tenancy"' "$groupname" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; adm) # All Admins (Basic goup with all admins) groupname="AllAdmins" groupdesc="Allow admins to read or use basic resources" printf '[\n' > ${scratch}.json printf ' "allow group %s to use tag-namespaces in tenancy",' "$groupname" >> ${scratch}.json if [ "$NetwName" != "" ]; then printf ' "allow group %s to use virtual-network-family in compartment %s",\n' "$groupname" "$NetwName" >> ${scratch}.json else printf ' "allow group %s to use virtual-network-family in compartment %s",\n' "$groupname" "$OpsName" >> ${scratch}.json fi printf ' "allow group %s to read all-resources in tenancy"' "$groupname" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; ops) groupname="OperationsAdmins" groupdesc="Allow operations admins to manage needed resources" printf '[\n' > ${scratch}.json printf ' "allow group %s to manage all-resources in tenancy"\n' "$groupname" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; netw) groupname="NetworkAdmins" groupdesc="Allow network admins to manage needed resources" printf '[\n' > ${scratch}.json printf ' "allow group %s to manage virtual-network-family in tenancy",\n' "$groupname" >> ${scratch}.json printf ' "allow group %s to manage all-resources in compartment %s"\n' "$groupname" "$NetwName" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; app) groupname="ApplicationAdmins" groupdesc="Allow application admins to manage needed resources" printf '[\n' > ${scratch}.json printf ' "allow group %s to manage all-resources in compartment %s"\n' "$groupname" "$AppName" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; db) groupname="DatabaseAdmins" groupdesc="Allow database admins to manage needed resources" printf '[\n' > ${scratch}.json printf ' "allow group %s to manage all-resources in compartment %s"\n' "$groupname" "$DbName" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; *) stat=1 esac if [ $stat -eq 0 -a "$groupname" != "" ]; then groupocid=`$jq -r '.[] | select(.name=="'$groupname'") | .id' $groupsfile` if [ "$groupocid" != "" ]; then echo "Group '$groupname' already exists." else echo "Creating group '$groupname'." CallOCI iam group create --name "$groupname" --description "$groupdesc" --defined-tags file://$labelfile \ --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 ]; then groupocid=`$jq -r '.[].id' $scratch` fi fi # Check if we found a valid ocid and add to blz json if [ "$groupocid" != "" ]; then groupocid=`echo "$groupocid" | grep "^ocid"` fi if [ "$groupocid" != "" ]; then printf '%s\t%s\t%s\n' "$groupid" "$groupocid" "$groupname" >> $output fi # Create policy groupocid=`$jq -r '.[] | select(.name=="'$groupname'") | .id' $policiesfile` if [ "$groupocid" != "" ]; then echo "Policy '$groupname' already exists." else echo "Creating policy '$groupname'." CallOCI iam policy create --compartment-id $OCI_TENANCY --name "$groupname" --description "$groupdesc" --statements file://${scratch}.json \ --defined-tags file://$labelfile --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 ]; then groupocid=`$jq -r '.[].id' $scratch` fi fi if [ "$groupocid" != "" ]; then printf '%s\t%s\t%s\n' "$groupid" "$groupocid" "$groupname" >> $poltsv fi fi } function GetPolicy { local policyid=${1} local policyname="" local policydesc="" local policyocid="" local subscribedregions="" local stat=0 local i=0 local max=0 case "$policyid" in savs) # Service Access for Vulnerability Scanning policyname="ServiceAccessVulnerabilityScanning" policydesc="Allow vulnerability scanning service to scan and manage e.g. instances" printf '[\n' > ${scratch}.json printf ' "allow service vulnerability-scanning-service to manage instances in tenancy",\n' >> ${scratch}.json printf ' "allow service vulnerability-scanning-service to read compartments in tenancy",\n' >> ${scratch}.json printf ' "allow service vulnerability-scanning-service to read vnics in tenancy",\n' >> ${scratch}.json printf ' "allow service vulnerability-scanning-service to read vnic-attachments in tenancy"\n' >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; sabs) # Service Access for Blockstorage policyname="ServiceAccessBlockStorage" policydesc="Allow blockstorage service to use private keys for encryption" printf '[\n' > ${scratch}.json printf ' "allow service blockstorage to use keys in compartment %s"\n' "$OpsName" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; saos) # Service Access for Objectstorage CallOCI iam region-subscription list > $scratch stat=$? if [ $stat -eq 0 -a -s $scratch ]; then subscribedregions=(`$jq -r '.[].regionName' $scratch`) else subscribedregions="($OCI_HOME_REGION)" stat=0 fi policyname="ServiceAccessObjectStorage" policydesc="Allow objectstorage service to manage e.g. lifecyle of objects in buckets and use private keys for encryption" printf '[\n' > ${scratch}.json max=${#subscribedregions[@]} if [ $max -gt 1 ]; then let "max -= 2" while [ $i -le $max ]; do printf ' "allow service objectstorage-%s to manage object-family in tenancy",\n' "${subscribedregions[$i]}" >> ${scratch}.json printf ' "allow service objectstorage-%s to use keys in compartment %s",\n' "${subscribedregions[$i]}" "$OpsName" >> ${scratch}.json let "i++" done fi printf ' "allow service objectstorage-%s to manage object-family in tenancy",\n' "${subscribedregions[$i]}" >> ${scratch}.json printf ' "allow service objectstorage-%s to use keys in compartment %s"\n' "${subscribedregions[$i]}" "$OpsName" >> ${scratch}.json printf ']\n' >> ${scratch}.json ;; *) stat=1 esac if [ $stat -eq 0 -a "$policyname" != "" ]; then policyocid=`$jq -r '.[] | select(.name=="'$policyname'") | .id' $policiesfile` if [ "$policyocid" != "" ]; then echo "Updating policy '$policyname'." CallOCI iam policy update --policy-id "$policyocid" --statements file://${scratch}.json --version-date "" --defined-tags file://$labelfile --force > $scratch stat=$? else echo "Creating policy '$policyname'." CallOCI iam policy create --compartment-id $OCI_TENANCY --name "$policyname" --description "$policydesc" --statements file://${scratch}.json \ --defined-tags file://$labelfile --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 ]; then policyocid=`$jq -r '.[].id' $scratch` fi fi if [ $stat -ne 0 ]; then echo "Unable to create or modify policy '$policyname'." else printf '%s\t%s\t%s\n' "$policyid" "$policyocid" "$policyname" >> $poltsv fi fi } function CreatePolicyAndGroups { local groupid="" printf '%s\t%s\t%s\n' "id" "ocid" "name" > ${scratch}.txt for groupid in csu rau adm ops; do GetGroup $groupid ${scratch}.txt done if [ "$NetwName" != "" ]; then GetGroup netw ${scratch}.txt fi if [ "$AppName" != "" ]; then GetGroup app ${scratch}.txt fi if [ "$DbName" != "" ]; then GetGroup db ${scratch}.txt fi # Extend the blz json result=`filecheck -sl ${scratch}.txt` if [ "$result" != "" ]; then ModifyJson "groups" ${scratch}.txt fi result=`filecheck -sl $poltsv` if [ "$result" != "" ]; then ModifyJson "policies" $poltsv fi } function AddLifecyclePolicy { local bucketname=${1} if [ "$bucketname" != "" ]; then # Create JSOn file with lifecycle policy rule printf '[\n {\n "action": "DELETE",\n "is-enabled": true,\n "name": "DeletionRule4TempFiles",\n' > ${scratch}.json printf ' "object-name-filter": {\n "exclusion-patterns": null,\n "inclusion-patterns": null,\n' >> ${scratch}.json printf ' "inclusion-prefixes": [\n "temp/"\n ]\n },\n "target": "objects",\n' >> ${scratch}.json printf ' "time-amount": 1,\n "time-unit": "DAYS"\n }\n]\n' >> ${scratch}.json CallOCI os object-lifecycle-policy put --bucket-name "$bucketname" --force --items file://${scratch}.json >$scratch stat=$? if [ $stat -eq 0 ]; then printf "Lifecycle policy for bucket '%s' created.\n" "$bucketname" else printf "Creation of lifecycle policy for bucket '%s' failed.\n" "$bucketname" fi fi } function CreateTagNamespaces { local tagocid="" local stat=0 # Get all groups and policies if [ ! -f "$tagsfile" ]; then CallOCI iam tag-namespace list --compartment-id $OCI_TENANCY > $tagsfile stat=$? fi if [ $stat -eq 0 ]; then printf '%s\t%s\t%s\n' "id" "ocid" "name" > ${scratch}.txt tagocid=`$jq -r '.[] | select(.name=="Oracle-Tags") | .id' $tagsfile` if [ "$tagocid" != "" ]; then tagocid=`echo "$tagocid" | grep "^ocid"` fi if [ "$tagocid" != "" ]; then printf '%s\t%s\t%s\n' "ot" "$tagocid" "Oracle-Tags" >> ${scratch}.txt fi tagocid=`$jq -r '.[] | select(.name=="Oracle-Standard") | .id' $tagsfile` if [ "$tagocid" != "" ]; then echo "Tag-Namespace 'Oracle-Standard' already exists." else echo "Importing Tag-Namespace 'Oracle-Standard'." # Import Oracle-Standard tags CallOCI iam tag import-standard-tags --compartment-id $OCI_TENANCY --standard-tag-namespace-name "Oracle-Standard" --wait-for-state SUCCEEDED > $scratch stat=$? if [ $stat -eq 0 -a -s $scratch ]; then tagocid=`$jq -r '.[] | select(.entityUri=="Oracle-Standard") | .identifier' $scratch` # Modify "Regulation" printf '{\n "validator-type": "ENUM",\n "values": [\n "HIPAA",\n "Sarbanes-Oxley (SOX)",\n "PCI-DSS",\n' > ${scratch}.json printf ' "GLBA",\n "GDPR",\n "DSGVO",\n "FISMA",\n "CCPA",\n "ISO",\n "SOC 1",\n "SOC 2",\n' >> ${scratch}.json printf ' "FedRamp",\n "NIST",\n "HITECH",\n "FERPA",\n "FACTA",\n "Texas HB300",\n "CIS",\n' >> ${scratch}.json printf ' "CJIS",\n "C-TPAT",\n "COPPA",\n "PIPEDA",\n "PIPL"\n ]\n}\n' >> ${scratch}.json CallOCI iam tag update --force --tag-namespace-id $tagocid --tag-name "Regulation" --validator file://${scratch}.json > $scratch stat=$? # Modify CostCenter CallOCI iam tag update --force --tag-namespace-id $tagocid --tag-name "CostCenter" --is-cost-tracking true > $scratch stat=$? # Add tag "Label" CallOCI iam tag create --tag-namespace-id $tagocid --name "Label" --description "Label the type of resource for cost tracking" --is-cost-tracking true > $scratch stat=$? fi fi if [ "$tagocid" != "" ]; then tagocid=`echo "$tagocid" | grep "^ocid"` fi if [ "$tagocid" != "" ]; then printf '%s\t%s\t%s\n' "os" "$tagocid" "Oracle-Standard" >> ${scratch}.txt fi # Extend the blz json result=`filecheck -sl ${scratch}.txt` if [ "$result" != "" ]; then ModifyJson "tags" ${scratch}.txt fi else echo "Unable to list tags. Error: '$stat'." fi } function GetCompartmentID { local compname=${1} local compdesc=${2} local filename=${3} local compocid="" local delocid="" local delcid="" local stat=1 result="" if [ "$compname" != "" -a -r "$filename" ]; then compocid=`$jq -r '.[] | select(.name=="'$compname'") | .id' "$filename"` if [ "$compocid" != "" ]; then # Compartment with specified name already exists echo "Compartment '$compname' already exists." result="$compocid" else # Compartment not found - first, look for deleted compartments CallOCI iam compartment list --compartment-id $OCI_TENANCY --all --compartment-id-in-subtree true --query 'data[?contains("lifecycle-state", `DELETED`)]' > $scratch stat=$? if [ $stat -eq 0 ]; then delocid=`$jq -r '.[0].id' $scratch` if [ "$delocid" = "null" ]; then delocid="" fi delcid=`$jq -r '.[0].compartmentId' $scratch` if [ "$delcid" = "null" ]; then delcid="" fi if [ "$delocid" != "" -a "$delcid" != "" ]; then echo "Creating compartment '$compname' using previously deleted compartment." # Deleted compartment found - recover it, rename it and move it to root if nescessary CallOCI iam compartment recover --compartment-id $delocid --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 ]; then # Rename it CallOCI iam compartment update --compartment-id $delocid --name "$compname" --description "$compdesc" --force \ --defined-tags file://$labelfile --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 -a "$delcid" != "$OCI_TENANCY" ]; then # Compartment is not in root - move it echo "Moving compartment '$compname' to root compartment." CallOCI iam compartment move --compartment-id $delocid --target-compartment-id $OCI_TENANCY --wait-for-state SUCCEEDED > $scratch stat=$? fi if [ $stat -eq 0 ]; then result="$delocid" fi fi else # Create new compartment echo "Creating compartment '$compname'." CallOCI iam compartment create --compartment-id "$OCI_TENANCY" --name "$compname" --description "$compdesc" \ --defined-tags file://$labelfile --wait-for-state ACTIVE > $scratch stat=$? if [ $stat -eq 0 ]; then result=`$jq -r '.[].id' $scratch` fi fi fi fi if [ "$result" = "" ]; then stat=1 fi fi return $stat } function GetCompartment { local compid=${1} local filename=${2} local compname="" local compdesc="" local stat=0 case "$compid" in ops) compname="$OpsName" compdesc="Contains all the operations related resources e.g. operator, bastion and vault" ;; netw) compname="$NetwName" compdesc="Contains all the network related resources" ;; app) compname="$AppName" compdesc="Contains all the application related resources" ;; db) compname="$DbName" compdesc="Contains all the database related resources" ;; sb) compname="Sandbox" compdesc="Test and Development playground" ;; *) stat=1 esac result="" if [ $stat -eq 0 -a "$compname" != "" ]; then GetCompartmentID "$compname" "$compdesc" "$filename" stat=$? if [ $stat -eq 0 ]; then # Check if we found a valid ocid result=`echo "$result" | grep "^ocid"` fi fi } # Create needed compartments (if the do not exist yet) function CheckCompartments { local stat=0 # Create a list of all compartments and sub compartments CallOCI iam compartment list --compartment-id $OCI_TENANCY --all --compartment-id-in-subtree true --query 'data[?contains("lifecycle-state", `ACTIVE`)]' > ${scratch}.txt stat=$? if [ $stat -eq 0 ]; then GetCompartment "ops" ${scratch}.txt operationsid="$result" GetCompartment "netw" ${scratch}.txt networkid="$result" GetCompartment "app" ${scratch}.txt applicationid="$result" GetCompartment "db" ${scratch}.txt dbid="$result" # GetCompartment "sb" ${scratch}.txt # dbid="$result" fi return $stat } function DeleteResource { local name=${1} local id="" local csocid="" local stat=0 if [ "$name" != "" ]; then $jq '.[] | select(.'$name') | .'$name'' "$blzjson" > $scratch stat=$? if [ $stat -eq 0 -a -s $scratch ]; then if [ "$name" = "users" ]; then # Grep ocid of Cloud Shell User csocid=`jq -r '.[] | select(.id=="csusr") | .ocid' $scratch` echo "Deleting all resources of type '$name' except 'csusr'." else echo "Deleting all resources of type '$name'." fi while read -r id; do if [ "$id" != "" ]; then case "$name" in images) CallOCI compute image delete --force --wait-for-state DELETED --image-id $id stat=$? ;; policies) CallOCI iam policy delete --force --wait-for-state DELETED --policy-id $id stat=$? ;; groups) CallOCI iam group delete --force --wait-for-state DELETED --group-id $id stat=$? ;; users) if [ "$id" != "$csocid" ]; then CallOCI iam user delete --force --wait-for-state DELETED --user-id $id stat=$? fi ;; esac else stat=1 fi if [ $stat -ne 0 ]; then echo "Unable to delete: '$id'." fi done < <($jq -r '.[].ocid' $scratch 2>/dev/null) # Delete resource from blz json ModifyJson "$name" "dummy" else echo "Resources of type '$name' already destroyed or never created." fi fi } # Check if we are running in CloudShell if [ "$OCI_CLI_CLOUD_SHELL" != "True" ]; then echo "Please run this script in CloudShell. Exiting." exitcode=1 else # Check for tooling CheckTools # Check if we have needed tools installed if [ "$curl" = "" -o "$oci" = "" -o "$jq" = "" ]; then echo "No 'curl', 'oci' or 'jq' in PATH. Exiting." exitcode=2 else # Check if we have administrator privileges CheckForAdminUser "$OCI_CS_USER_OCID" exitcode=$? if [ $exitcode -ne 0 ]; then exitcode=3 echo "Need to be in group 'Administrators'. Exiting." else # Cleanup temp files Cleanup # Create logdir and oci dir if [ ! -d "$logdir" -o ! -d "$ocifolder" ]; then mkdir -m 0755 -p "$logdir" "$ocifolder" fi # Create new empty log if RotateLog = true if [ "$RotateLog" = "true" ]; then printf "" > "$logfile" else touch "$logfile" fi # Write start time to log WriteLog "Tool '$progstr' started." if [ "$param" = "remove" ]; then if [ -r "$blzjson" ]; then # Remove previously deployed resources printf '\n' echo "$progstr $param" | toupper | print-header printf '\n' # Get namespace GetNamespace # Find all deployed resources for rtype in images policies groups users; do DeleteResource $rtype done # Remove blz jason file and .admintools file DeleteFile "$blzjson" DeleteFile "$tcfgfile" # Print on screen message printf '\nMost of the resources were deleted except: Tag-Namespaces and Compartments.\n' echo "Optionally remove installed bucket, scripts and tools interactive with:" echo " setup-tools remove" else exitcode=5 echo "Unable to read '$blzjson' with prevously deployed resources. Exiting." fi else # Check for internet connection CheckInternet # Create file with tagging information printf '{\n "Oracle-Standard": {\n "Label": "%s"\n }\n}\n' "$Label" > $labelfile # Install scripts (if not installed) if [ "$setuptools" = "" ]; then WriteLog "Installing admin scripts." "/tmp/$scriptsfile" DeleteFile "/home/opc/.bash_profile.old" else printf "\nAdmin scripts already installed.\n" fi # Install tools (if not installed) if [ "$rclone" = "" ]; then DownloadFile "$toolsfile" "false" stat=$? if [ $stat -eq 0 ]; then WriteLog "Installing tools." "/tmp/$toolsfile" # Check for tooling again CheckTools fi else echo "Tools already installed. Update with 'setup-tools update' if needed." fi # Tools installed - now configure them printf '\n' echo "$progstr config" | toupper | print-header printf '\n' # Get namespace GetNamespace # Tag-Namespace and Tags CreateTagNamespaces # Get Tenancy Name and Home Region CallOCI iam tenancy get --tenancy-id $OCI_TENANCY >$scratch stat=$? if [ $stat -eq 0 ]; then OCI_TENANCY_NAME=`cat $scratch | $jq -r '.[].name'` OCI_HOME_REGION_KEY=`cat $scratch | $jq -r '.[].homeRegionKey'` if [ "$OCI_HOME_REGION_KEY" != "" ]; then CallOCI iam region list --all > $scratch stat=$? if [ $stat -eq 0 ]; then OCI_HOME_REGION=`cat $scratch | $jq -r '.[] | select(.key=="'$OCI_HOME_REGION_KEY'") | .name'` fi fi fi # Get a list of all existing users CallOCI iam user list --all > $usersfile stat=$? if [ $stat -eq 0 -a -s "$usersfile" ]; then # Create all needed users and technical users for cloud administration (if not already created) CreateAllAdminUsers else exitcode=5 errormsg $exitcode "Unknown condition. Could not list users. Exiting." Cleanup exit $exitcode fi # Create a file with all existing keys (before key creation) key-management list tsv | tailfromline2 > $keysfile # # Debug # echo "OCI_CLI_CLOUD_SHELL: '$OCI_CLI_CLOUD_SHELL'." # echo "OCI_TENANCY: '$OCI_TENANCY'." # echo "OCI_TENANCY_NAME: '$OCI_TENANCY_NAME'." # echo "OCI_REGION: '$OCI_REGION'." # echo "OCI_HOME_REGION_KEY: '$OCI_HOME_REGION_KEY'." # echo "OCI_HOME_REGION: '$OCI_HOME_REGION'." # echo "OCI_CS_USER_OCID: '$OCI_CS_USER_OCID'." # echo "OCI_CS_USER_NAME: '$OCI_CS_USER_NAME'." # echo "OCI_CLOUD_ADMIN_NAME: '$OCI_CLOUD_ADMIN_NAME'." # echo "OCI_CLOUD_ADMIN_OCID: '$OCI_CLOUD_ADMIN_OCID'." # echo "OCI_ADMINISTRATORS_OCID: '$OCI_ADMINISTRATORS_OCID'." # echo "OCI_NAMESPACE: '$OCI_NAMESPACE'." # cat $keysfile # Cleanup # exit # Create keys for api access (oci user) CreateKeys "$OCI_CLOUD_ADMIN_NAME" "$imp_apikey" # Create keys for tenancy access (opc user) CreateKeys "$OCI_TENANCY_NAME" "$imp_tenancykey" # Create keys for jumphost user CreateKeys "$jumphostname" "$imp_jumphostkey" # Create a file with all existing keys (after key creation) key-management list tsv | tailfromline2 > $keysfile # Check if API key is attached to user if [ "$OCI_CLOUD_ADMIN_NAME" != "" -a "$OCI_CLOUD_ADMIN_OCID" != "" ]; then apifp=`cat $keysfile | grep "^${OCI_CLOUD_ADMIN_NAME} " | cut -d$'\t' -f3` apikey=`cat $keysfile | grep "^${OCI_CLOUD_ADMIN_NAME} " | cut -d$'\t' -f4` if [ -r "${apikey}.pk8" ]; then # Prefering this key apikey="${apikey}.pk8" fi CallOCI iam user api-key list --user-id $OCI_CLOUD_ADMIN_OCID > $scratch stat=$? else exitcode=5 errormsg $exitcode "Unknown condition. Could not get all variables. Exiting." Cleanup exit $exitcode fi if [ $stat -eq 0 -a "$apifp" != "" ]; then found="false" result=`cat $scratch | $jq -r '.[].fingerprint'` for fp in $result; do if [ "$fp" = "$apifp" ]; then found="true" fi done if [ "$found" = "false" ]; then echo "Attaching API Key." key-management show pem --username "$OCI_CLOUD_ADMIN_NAME" | grep . > $scratch CallOCI iam user api-key upload --user-id $OCI_CLOUD_ADMIN_OCID --key-file $scratch > /dev/null else echo "API key already attached to user." fi fi # Get jumphost key and tenancy keys (opc user) jumphostkey=`cat $keysfile | grep "^${jumphostname} " | cut -d$'\t' -f4` tenancykey=`cat $keysfile | grep "^${OCI_TENANCY_NAME} " | cut -d$'\t' -f4` if [ -r "${tenancykey}.pub" ]; then # Public key tenancykeypub="${tenancykey}.pub" fi # Convert $HOME to "~" in keys to use in json config files if [ "$apikey" != "" -a "$tenancykey" != "" -a "$tenancykeypub" != "" -a "$jumphostkey" != "" ]; then apikey=`echo "$apikey" | sed 's|'"$HOME"'|~|'` tenancykey=`echo "$tenancykey" | sed 's|'"$HOME"'|~|'` tenancykeypub=`echo "$tenancykeypub" | sed 's|'"$HOME"'|~|'` jumphostkey=`echo "$jumphostkey" | sed 's|'"$HOME"'|~|'` # Write keys to blz json printf '%s\t%s\t%s\n' "id" "file" "name" > ${scratch}.txt printf '%s\t%s\t%s\n' "oci" "$apikey" "$OCI_CLOUD_ADMIN_NAME" >> ${scratch}.txt printf '%s\t%s\t%s\n' "opc" "$tenancykey" "$OCI_TENANCY_NAME" >> ${scratch}.txt printf '%s\t%s\t%s\n' "jh" "$jumphostkey" "$jumphostname" >> ${scratch}.txt ModifyJson "keys" ${scratch}.txt fi # Create secret key and if successful, create oci json file if [ ! -r "$ocijson" ]; then CallOCI iam customer-secret-key create --display-name "S3 Access" --user-id $OCI_CLOUD_ADMIN_OCID > $scratch stat=$? if [ $stat -eq 0 ]; then echo "Secret key created." accesskeyid=`cat $scratch | $jq -r '.[].id'` secretaccesskey=`cat $scratch | $jq -r '.[].key'` # Create json file touch $ocijson chmod 600 $ocijson printf '{\n' > $ocijson printf ' "oci": {\n' >> $ocijson printf ' "tenancyname": "%s",\n' "$OCI_TENANCY_NAME" >> $ocijson printf ' "tenancyid": "%s",\n' "$OCI_TENANCY" >> $ocijson printf ' "homeregion": "%s",\n' "$OCI_HOME_REGION" >> $ocijson printf ' "homeregionkey": "%s"\n' "$OCI_HOME_REGION_KEY" >> $ocijson printf ' },\n' >> $ocijson printf ' "keys": {\n' >> $ocijson printf ' "username": "%s",\n' "$OCI_CLOUD_ADMIN_NAME" >> $ocijson printf ' "userid": "%s",\n' "$OCI_CLOUD_ADMIN_OCID" >> $ocijson printf ' "apikey": "%s",\n' "$apikey" >> $ocijson printf ' "fingerprint": "%s",\n' "$apifp" >> $ocijson printf ' "passphrase": "",\n' >> $ocijson printf ' "privkey": "%s",\n' "$tenancykey" >> $ocijson printf ' "pubkey": "%s"\n' "$tenancykeypub" >> $ocijson printf ' },\n' >> $ocijson printf ' "s3": {\n' >> $ocijson printf ' "namespace": "%s",\n' "$OCI_NAMESPACE" >> $ocijson printf ' "accesskey": "%s",\n' "$accesskeyid" >> $ocijson printf ' "secretkey": "%s"\n' "$secretaccesskey" >> $ocijson printf ' }\n' >> $ocijson printf '}\n' >> $ocijson else exitcode=5 errormsg $exitcode "Unknown condition. Could not create secret key. Exiting." Cleanup exit $exitcode fi else echo "Secret key is already configured." fi # Create needed compartments (if the do not exist yet) CheckCompartments stat=$? if [ $stat -eq 0 -a "$operationsid" != "" ]; then # Write compartments to blz json printf '%s\t%s\t%s\n' "id" "ocid" "name" > ${scratch}.txt printf '%s\t%s\t%s\n' "ops" "$operationsid" "$OpsName" >> ${scratch}.txt if [ "$NetwName" != "" ]; then printf '%s\t%s\t%s\n' "netw" "$networkid" "$NetwName" >> ${scratch}.txt fi if [ "$AppName" != "" ]; then printf '%s\t%s\t%s\n' "app" "$applicationid" "$AppName" >> ${scratch}.txt fi if [ "$DbName" != "" ]; then printf '%s\t%s\t%s\n' "db" "$dbid" "$DbName" >> ${scratch}.txt fi ModifyJson "compartments" ${scratch}.txt else exitcode=5 errormsg $exitcode "Unknown condition. Could not list compartments. Exiting." Cleanup exit $exitcode fi # Get all groups and policies CallOCI iam group list --all > $groupsfile stat=$? # Get all policies if [ $stat -eq 0 ]; then CallOCI iam policy list --compartment-id $OCI_TENANCY --all > $policiesfile stat=$? fi # Add basic policies if [ $stat -eq 0 -a -s "$groupsfile" -a -s "$policiesfile" ]; then printf '%s\t%s\t%s\n' "id" "ocid" "name" > $poltsv GetPolicy saos GetPolicy sabs GetPolicy savs else exitcode=5 errormsg $exitcode "Unknown condition. Could not list groups or policies. Exiting." Cleanup exit $exitcode fi # Configure tools and create project json if [ $stat -eq 0 -a ! -r "$projectjson" ]; then # Let setup-tools do the rest of configuration $setuptools config # Modify bucket CallOCI os bucket update --bucket-name "$OpsName" --defined-tags file://$labelfile >/dev/null stat=$? if [ $stat -eq 0 ]; then AddLifecyclePolicy "$OpsName" fi else echo "Tools were already configured." fi # Switch user if [ -r "$tcfgfile" ]; then source "$tcfgfile" # Check if new config works by calling GetNamespace GetNamespace fi # Deploy basic policies and other resources printf '\n' echo "$progstr deploy" | toupper | print-header printf '\n' # Policies and goups CreatePolicyAndGroups # Master images CreateCustomImages fi fi fi fi # Cleanup and exit Cleanup exit $exitcode