#!/usr/bin/env bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)oci-api 3.2.1 07.11.2025 (c)2025 Standby.cloud # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ # # Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. # This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or # Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license. # #@ This script is a wrapper for the OCI REST API. #@ #@Usage: oci-api [options] endpoint method [filename] request #@ Options: #@ -h, --help : Displays helptext. #@ -v, --version: Displays the version of the script. #@ Endpoint: REST endpoint e.g. "iaas.eu-frankfurt-1.oraclecloud.com". #@ Method : get | head | post | put | delete. #@ Filename: Optional filename needed together with post or put #@ Request : Request that is appended to endpoint e.g. "/20160918/volumeAttachments?compartmentId=$cid" #@ #@Examples: #@ List compartments: #@ oci-api identity.eu-frankfurt-1.oraclecloud.com get /20160918/compartments/?compartmentId=ocid1.tenancy.oc1..amgxr35ltsgdy63lzts6jm2qtil7ug2wpm6uc2kweau6det # # Doku: https://docs.cloud.oracle.com/de-de/iaas/Content/API/Concepts/usingapi.htm # API Reference: https://docs.cloud.oracle.com/en-us/iaas/api/ # # Usage: # oci-curl.bash [file-to-send-as-body] [extra-curl-args] # # JSON output is always enveloped with "items" and amount of items (totalItems). Disable this via 'export ENVELOPE_API=false' # # Update history: # # V 3.2.0 11.08.2024 New minor version # V 3.2.1 07.11.2025 Working with instance principal # # Preset: # # export OCI_TENANCY=`browse-json oci/tenancyid --select 1 --raw --import ~/.oci/config.json` # export API_USER_ID=`browse-json keys/userid --select 1 --raw --import ~/.oci/config.json` # export API_USER=`browse-json keys/username --select 1 --raw --import ~/.oci/config.json` # export API_KEY_FINGERPRINT=`browse-json keys/fingerprint --select 1 --raw --import ~/.oci/config.json` # export API_KEY_FILE=`browse-json keys/privkey --select 1 --raw --import ~/.oci/config.json` # export API_KEY_FILE=$(eval echo ${API_KEY_FILE}) # # If private key is encrypted, a file "~/.oci/api-passwd" with the password in the first line is expected - # otherwise you are prompted for the passphrase. # # Examples: # # Get all instance data: curl -L -sS http://169.254.169.254/opc/v1/instance # Get all instance vnics: curl -L -sS http://169.254.169.254/opc/v1/vnics # Get all instance volume attachments: curl -L -sS http://169.254.169.254/opc/v1/volumeAttachments # Get all instance certificates: curl -L -sS http://169.254.169.254/opc/v1/identity # # Get instance internal ip: curl -sS http://169.254.169.254/opc/v1/vnics/0/privateIp # Get instance metadata: curl -L -sS http://169.254.169.254/opc/v1/instance/metadata # Get instance metadata: curl -L -sS http://169.254.169.254/openstack/latest/meta_data.json # # region=`curl -sS http://169.254.169.254/opc/v1/instance/region` # Get region from instance # iid=`curl -sS http://169.254.169.254/opc/v1/instance/id` # Get the instanceId from instance # cid=`curl -sS http://169.254.169.254/opc/v1/instance/compartmentId` # Get the compartmentId from instance # # Write certificates from instance to file: # curl -sS http://169.254.169.254/opc/v1/identity/cert.pem -o cert.pem # curl -sS http://169.254.169.254/opc/v1/identity/key.pem -o key.pem # curl -sS http://169.254.169.254/opc/v1/identity/intermediate.pem -o intermediate.pem # # List compartments # oci-api identity.${OCI_HOME_REGION}.$OCI_DEFAULT_REALM get "/20160918/compartments/?compartmentId=$OCI_TENANCY" # # List all instances in compartment # oci-api iaas.eu-frankfurt-1.oraclecloud.com get "/20160918/instances?compartmentId=$cid" | convert-json --output json # # List single instance # oci-api iaas.${OCI_REGION}.$OCI_DEFAULT_REALM get "/20160918/instances/$iid" | jq -M . # # List volume attachment # oci-api iaas.eu-frankfurt-1.oraclecloud.com get "/20160918/volumeAttachments?compartmentId=$cid" | jq -M . # oci-api iaas.eu-frankfurt-1.oraclecloud.com get "/20160918/volumeAttachments?compartmentId=$cid&instanceId=$iid" | jq -M . # # oci-api iaas.eu-frankfurt-1.oraclecloud.com post ./request.json "/20160918/vcns" # # Using oci cli instead: # # set -e # # if [[ -z "$COMPARTMENT_ID" ]];then # echo "COMPARTMENT_ID must be defined in the environment. " # exit 1 # fi # # USER_NAME="TestUser" # USER_DESCRIPTION="User created by raw request" # TARGET_URI='https://identity.us-phoenix-1.oraclecloud.com/20160918/users/' # HTTP_METHOD='POST' # PROFILE='ADMIN' # REQUEST_BODY="{\"compartmentId\": \"$COMPARTMENT_ID\", \"name\": \"$USER_NAME\", \"description\": \"$USER_DESCRIPTION\"}" # # echo "oci raw-request --profile ${PROFILE} --target-uri ${TARGET_URI} --http-method ${HTTP_METHOD} --request-body "${REQUEST_BODY}" | jq -r '.data.id'" # USER_OCID=$(oci raw-request --profile ${PROFILE} --target-uri ${TARGET_URI} --http-method ${HTTP_METHOD} --request-body "${REQUEST_BODY}" | jq -r '.data.id') # echo "Created user OCID: $USER_OCID" # # Another example: # oci raw-request --auth instance_principal --http-method GET --target-uri "https://iaas.eu-frankfurt-1.oraclecloud.com/20160918/volumeAttachments?compartmentId=$cid&instanceId=$iid" # # Exit codes: # 001: Unknown or wrong parameter. # 002: Exit code from curl (2 - 98) # 099: User interrupt. # 100: No 'curl' or 'jq' in PATH # 101: External variables not set (e.g. API_USER_ID) # 102: Head returned other code then 200 (ok) # 103: No variable "code" in JSON # 104: Empty body file # 105: S3 not yet implemented # Find executable bash library and source it lib=`which lib.bash 2>/dev/null | sed 's|^no 'lib.bash' in .*||'` if [ "$lib" != "" ]; then source "$lib" else progdir=`dirname "$0"` if [ -r "${progdir}/lib.bash" ]; then source "${progdir}/lib.bash" else echo "Unexpected error: Unable to locate bash library 'lib.bash'." exit 1 fi fi ## Preset #readonly toolsdir="admintools" # ## Source environment variables #if [ -r "${HOME}/.$toolsdir" ]; then # source "${HOME}/.$toolsdir" #else # if [ -r "/usr/local/share/info/.$toolsdir" ]; then # source "/usr/local/share/info/.$toolsdir" # fi #fi # Do extra cleanup function ExtraCleanup { filecheck -rm ${scratchfile}.jbody filecheck -rm ${scratchfile}.head filecheck -rm ${scratchfile}.pre filecheck -rm ${scratchfile}.out } # Return the fingerprint of a private key (which is not encrypted) function GetFingerprint { local privkey=${1} local md5sum=`filecheck -x md5sum` if [ "$md5sum" != "" -a "$privkey" != "" -a -s "$privkey" ]; then $openssl rsa -inform PEM -in "$privkey" -pubout -outform DER 2>/dev/null | $md5sum | cut -d' ' -f1 | sed 's|..|\:&|2g' fi } # Return the fingerprint of the instance private key function GetInstanceFingerprint { local md5sum=`filecheck -x md5sum` if [ "$md5sum" != "" ]; then InstanceInfo "identity/key.pem" | $openssl rsa -inform PEM -pubout -outform DER 2>/dev/null | $md5sum | cut -d' ' -f1 | sed 's|..|\:&|2g' fi } function KeyIsEncrypted { local privkey=${1} local encrypted="" if [ "$privkey" != "" -a -s "$privkey" ]; then encrypted=`grep "ENCRYPTED" "$privkey"` fi if [ "$encrypted" != "" ]; then return "true" else return "false" fi } # Needed by s3-curl function sha256 { local str=${1} printf "$str" | $openssl dgst -sha256 -hex | sed 's/SHA2-256(stdin)= //g' } # Needed by s3-curl function sign { local keyArg=${1} local msg=${2} # printf "${msg}" | $openssl dgst -sha256 -mac hmac -macopt "hexkey:${hexKey}" | sed 's/^.* //' printf "${msg}" | $openssl dgst -sha256 -mac hmac -macopt "${keyArg}" | sed 's/SHA2-256(stdin)= //g' } # Needed by s3-curl function getSignatureKey { local key=${1} local dateStamp1=${2} local regionName=${3} local serviceName=${4} local kDate kRegion kService kSigning kDate="$(sign "key:AWS4${key}" "${dateStamp1}")" # echo "kDate=$kDate" >&2 kRegion="$(sign "hexkey:${kDate}" "${regionName}")" kService="$(sign "hexkey:${kRegion}" "${serviceName}")" kSigning="$(sign "hexkey:${kService}" "aws4_request")" printf "${kSigning}" } # Access S3 compat-objectstorage REST API via curl # Source: https://gist.github.com/slawekzachcial/fe23184124763dfb82f233b5dde2394b # AWS Signing: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html # Check: https://medium.com/@pratapgowda007/accessing-oci-object-storage-through-the-amazon-s3-compatibility-api-403fcfb54f3e # Check: https://towardsaws.com/aws-sigv4-in-3-mins-c324d20f19cf function s3-curl { local host=${1} local method=${2} local request=${3} local region=`echo "$host" | cut -d'.' -f4` local endpoint="https://${host}$request" local service="s3" local contentType="application/x-amz-json-1.1" local amazonDate="$(date --utc +'%Y%m%dT%H%M%SZ')" local dateStamp="$(date --utc +'%Y%m%d')" local result="" local stat=0 local requestParameters="" # local parameterName="location" # local requestParameters="$(printf '{"Name":"%s","WithDecryption":true}' "${parameterName}")" # local amazonTarget="AmazonSSM.GetParameter" result=`echo "$request" | grep '?'` if [ "$result" != "" ]; then requestParameters=`echo "$request" | cut -d'?' -f2` request=`echo "$request" | cut -d'?' -f1` # endpoint="https://${host}$request" fi if [ "$DEBUG_API" = true ]; then printf "\nS3 Test:\n\n" echo "host: '$host'." echo "method: '$method'." echo "request: '$request'." echo "requestParameters: '$requestParameters'." echo "region: '$region'." echo "endpoint: '$endpoint'." printf "\n" fi # --- TASK 1: create canonical request --- readonly canonicalUri="$request" readonly canonicalQueryString="" readonly payloadHash="$(sha256 "${requestParameters}")" readonly canonicalHeaders="host:${host}\nx-amz-content-sha256:${payloadHash}\nx-amz-date:${amazonDate}\n" readonly signedHeaders="host;x-amz-content-sha256;x-amz-date" readonly canonicalRequest="${method}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}" # --- TASK 2: create the string to sign --- readonly algorithm="AWS4-HMAC-SHA256" readonly credentialScope="${dateStamp}/${region}/${service}/aws4_request" readonly stringToSign="${algorithm}\n${amazonDate}\n${credentialScope}\n$(sha256 "${canonicalRequest}")" # --- TASK 3: calculate the signature --- readonly signingKey="$(getSignatureKey "${S3_SECRETKEY}" "${dateStamp}" "${region}" "${service}")" readonly signature="$(sign "hexkey:${signingKey}" "${stringToSign}")" # --- TASK 4: add signing information to the request --- readonly authorizationHeader="${algorithm} \ Credential=${S3_ACCESSKEY}/${credentialScope}, \ SignedHeaders=${signedHeaders}, \ Signature=${signature}" # --- SEND REQUEST --- if [ "$DEBUG_API" = true ]; then echo -e "payloadHash: '$payloadHash'." echo -e "canonicalHeaders: '$canonicalHeaders'." echo -e "canonicalRequest: '$canonicalRequest'." echo -e "stringToSign: '$stringToSign'." fi $curl \ "${endpoint}" \ --header "Authorization: ${authorizationHeader}" \ --header "x-amz-content-sha256: ${payloadHash}" \ --header "x-amz-date: ${amazonDate}" \ -sS -D ${scratchfile}.head > $scratchfile # --- CHECK RESULT --- stat=$? if [ "$DEBUG_API" = true ]; then if [ $stat -eq 0 ]; then printf "\nResult:\n\n" cat $scratchfile | pup # 'json{}' | "$jq" -M printf "\n" fi fi errormsg 0 "($progstr) S3 compatible objectstorage not implemented (curl code: $stat)." return 105 } # Access OCI REST API via curl # Source: https://docs.cloud.oracle.com/iaas/Content/API/Concepts/signingrequests.htm#Bash # Signing: https://www.ateam-oracle.com/post/oracle-cloud-infrastructure-oci-rest-call-walkthrough-with-curl function oci-curl { local host=${1} local method=${2} local body # ${3} local target # ${3} or ${4} local curl_method=`echo "$method" | toupper` local stat=1 local i=0 local alg="rsa-sha256" local sigVersion="1" local now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")" local date_header="date: $now" local host_header="host: $host" local curl_header_args=(-H "$date_header" -H "$host_header") local body_arg=() local extra_args local keyId local token local delegationtoken local sig local sel="" local npg="" local qm="" case $method in get) target=${3} extra_args=("${@: 4}") ;; delete) target=${3} extra_args=("${@: 4}") ;; head) target=${3} extra_args=("--head" "${@: 4}") curl_method="GET" ;; post | put) body=${3} target=${4} extra_args=("${@: 5}") local content_sha256="$($openssl dgst -binary -sha256 < $body | $openssl enc -e -base64)" local content_type="application/json" local content_length="$(wc -c < $body | xargs)" ;; *) echo "invalid method '$method'." return $stat esac # Append page selector to target if nescessary if [ "$np" != "" -o "$ns" != "" ]; then if [ "$conversion" = "objects" ]; then sel="start" npg="$ns" else sel="page" npg="$np" fi qm=`echo "$target" | grep '?'` if [ "$qm" = "" ]; then target="${target}?${sel}=$npg" else target="${target}&${sel}=$npg" fi fi # This line will url encode all special characters in the request target except "/", "?", "=", and "&", since those characters are used # in the request target to indicate path and query string structure. If you need to encode any of "/", "?", "=", or "&", such as when # used as part of a path value or query string key or value, you will need to do that yourself in the request target you pass in. local escaped_target="$(echo $( rawurlencode "$target" ))" local request_target="(request-target): $method $escaped_target" if [ "$method" = "post" -o "$method" = "put" ]; then local content_sha256_header="x-content-sha256: $content_sha256" local content_type_header="content-type: $content_type" local content_length_header="content-length: $content_length" fi local headers="date (request-target) host" local signing_string="$date_header\n$request_target\n$host_header" if [ "$OCI_AUTH_TYPE" = "instance_principal" -a "$OCI_DELEGATION_TOKEN_FILE" != "" -a -s "$OCI_DELEGATION_TOKEN_FILE" ]; then headers="$headers opc-obo-token" delegationtoken=`cat "$OCI_DELEGATION_TOKEN_FILE"` delegation_header="opc-obo-token: $delegationtoken" signing_string="$signing_string\n$delegation_header" curl_header_args=("${curl_header_args[@]}" -H "$delegation_header") fi if [ "$method" = "post" -o "$method" = "put" ]; then signing_string="$signing_string\n$content_sha256_header\n$content_type_header\n$content_length_header" headers=$headers" x-content-sha256 content-type content-length" curl_header_args=("${curl_header_args[@]}" -H "$content_sha256_header" -H "$content_type_header" -H "$content_length_header") body_arg=(--data-binary @${body}) fi if [ "$OCI_AUTH_TYPE" = "instance_principal" ]; then sig=$(printf '%b' "$signing_string" | \ $openssl dgst -sha256 -sign "$INSTANCE_KEY_FILE" | \ $openssl enc -e -base64 | tr -d '\n') else # Check if we need a passphrase to encrypt if [ ! -s "${HOME}/.oci/api-passwd" ]; then sig=$(printf '%b' "$signing_string" | \ $openssl dgst -sha256 -sign $API_KEY_FILE | \ $openssl enc -e -base64 | tr -d '\n') else # -passin "env:API_KEY_PASS" would be less secure sig=$(printf '%b' "$signing_string" | \ $openssl dgst -sha256 -passin "file:${HOME}/.oci/api-passwd" -sign $API_KEY_FILE | \ $openssl enc -e -base64 | tr -d '\n') fi fi while [ $i -lt 2 ]; do if [ "$OCI_AUTH_TYPE" = "instance_principal" -a "$INSTANCE_TOKEN_FILE" != "" -a -s "$INSTANCE_TOKEN_FILE" ]; then token=`cat "$INSTANCE_TOKEN_FILE"` keyId="ST\$$token" else keyId="$OCI_TENANCY/$API_USER_ID/$API_KEY_FINGERPRINT" fi $curl "${extra_args[@]}" "${body_arg[@]}" -X $curl_method -sS "https://${host}${escaped_target}" "${curl_header_args[@]}" \ -H "Authorization: Signature algorithm=\"$alg\",headers=\"${headers}\",keyId=\"$keyId\",signature=\"$sig\",version=\"$sigVersion\"" \ --connect-timeout 10 --max-time 60 --retry 3 --retry-delay 5 -D ${scratchfile}.head > $scratchfile 2>/dev/null stat=$? let i++ if [ "$OCI_AUTH_TYPE" = "instance_principal" -a $exitcode -eq 0 -a -s ${scratchfile}.head ]; then # Check if tokes was maybe invalid result=`head -n 1 ${scratchfile}.head | grep "Unauthorized"` if [ "$result" != "" ]; then GetToken else let i++ fi else let i++ fi done return $stat } # Get a token with cert and key from instance function GetToken { local alg="rsa-sha256" local sigVersion="1" local curl_method="POST" local method=`echo "$curl_method" | tolower` local now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")" local target="/v1/x509" local content_type="application/json" local stat=1 # Get info from temp files local cert=`cat $INSTANCE_CERT_FILE | grep -v "^--" | tr -d '\n'` local intermediate=`cat $INSTANCE_INTERMEDIATE_FILE | grep -v "^--" | tr -d '\n'` local public=`$openssl rsa -in "$INSTANCE_KEY_FILE" -pubout -outform DER 2>/dev/null | base64 -w0` local fingerprint=`$openssl x509 -in $INSTANCE_CERT_FILE -noout -fingerprint | cut -d"=" -f2` local host="auth.${INSTANCE_REGION}.$INSTANCE_REALM" local keyId="${INSTANCE_TENANT_ID}/fed-x509/$fingerprint" # Create certificate JSON printf '{"certificate": "%s", "publicKey": "%s", "intermediateCertificates": ["%s"]}' "$cert" "$public" "$intermediate" > ${scratchfile}.jbody local content_sha256="$($openssl dgst -binary -sha256 < ${scratchfile}.jbody | $openssl enc -e -base64)" local content_length="$(wc -c < ${scratchfile}.jbody | xargs)" local escaped_target="$(echo $( rawurlencode "$target" ))" local date_header="date: $now" local request_target="(request-target): $method $escaped_target" local host_header="host: $host" local content_length_header="content-length: $content_length" local content_type_header="content-type: $content_type" local content_sha256_header="x-content-sha256: $content_sha256" local signing_string="$date_header\n$request_target\n$host_header\n$content_length_header\n$content_type_header\n$content_sha256_header" local headers="date (request-target) host content-length content-type x-content-sha256" local curl_header_args=(-H "$date_header" -H "$host_header" -H "$content_length_header" -H "$content_type_header" -H "$content_sha256_header") local body_arg=(--data-binary @${scratchfile}.jbody) local sig=$(printf '%b' "$signing_string" | \ $openssl dgst -sha256 -sign "$INSTANCE_KEY_FILE" | \ $openssl enc -e -base64 | tr -d '\n') $curl --connect-timeout 10 --max-time 60 --retry 3 --retry-delay 5 "${body_arg[@]}" -X $curl_method -sS "https://${host}${escaped_target}" "${curl_header_args[@]}" \ -H "Authorization: Signature algorithm=\"$alg\",headers=\"${headers}\",keyId=\"$keyId\",signature=\"$sig\",version=\"$sigVersion\"" > $scratchfile 2>/dev/null stat=$? if [ $stat -eq 0 -a -s $scratchfile ]; then $jq -r '.token' $scratchfile > "$INSTANCE_TOKEN_FILE" stat=$? chmod 600 "$INSTANCE_TOKEN_FILE" # echo "New Token created." >> /tmp/result fi Cleanup return $stat } # Use token created by GetToken # Only for testing - Allowd method = "get" function UseToken { local host=${1} local method=${2} local target=${3} local alg="rsa-sha256" local sigVersion="1" local curl_method=`echo "$method" | toupper` local now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")" local escaped_target="$(echo $( rawurlencode "$target" ))" local request_target="(request-target): $method $escaped_target" local date_header="date: $now" local host_header="host: $host" local headers="date (request-target) host" local curl_header_args=(-H "$date_header" -H "$host_header") local content_type="application/json" local token=`cat "$INSTANCE_TOKEN_FILE"` local delegationtoken="" local signing_string="$date_header\n$request_target\n$host_header" local keyId="ST\$$token" local stat=1 if [ "$OCI_CLI_AUTH" = "instance_obo_user" ]; then headers="$headers opc-obo-token" delegationtoken=`cat "$OCI_DELEGATION_TOKEN_FILE"` delegation_header="opc-obo-token: $delegationtoken" curl_header_args=(-H "$date_header" -H "$host_header" -H "$delegation_header") signing_string="$date_header\n$request_target\n$host_header\n$delegation_header" fi local sig=$(printf '%b' "$signing_string" | \ $openssl dgst -sha256 -sign "$INSTANCE_KEY_FILE" | \ $openssl enc -e -base64 | tr -d '\n') $curl --connect-timeout 10 --max-time 60 --retry 3 --retry-delay 5 -X $curl_method -sS "https://${host}${escaped_target}" "${curl_header_args[@]}" \ -H "authorization: Signature algorithm=\"$alg\",headers=\"${headers}\",keyId=\"$keyId\",signature=\"$sig\",version=\"$sigVersion\"" \ -D ${scratchfile}.head > $scratchfile 2>/dev/null stat=$? return $stat } # Check if api is configurered - if not, try to get as much infos as possible # Allowed values for OCI_CLI_AUTH: api_key, instance_obo_user, instance_principal, resource_principal, security_token function CheckAPIConfig { local configdir="${HOME}/.oci" local configfile="${configdir}/api-config" local passwdfile="${configdir}/api-passwd" local instancedir="${configdir}/instance" local instancefile="${instancedir}/config" local instancekey="${instancedir}/key.pem" local instancecert="${instancedir}/cert.pem" local instanceinter="${instancedir}/intermediate.pem" local instancetoken="${instancedir}/token" local encrypted="" local iid="" local fp="" local tenancy="" local region="" local realm="" local cid="" local avd="" local stat=1 if [ ! -d "$configdir" ]; then mkdir -p "$configdir" chmod 700 "$configdir" fi if [ ! -d "$instancedir" ]; then mkdir -p "$instancedir" chmod 700 "$instancedir" fi if [ -s "$configfile" -a "$OCI_HOME_REGION" = "" ]; then # echo "source api-config" >> /tmp/result source "$configfile" fi # Try to determine authorizatin type if not defined if [ "$OCI_CLI_AUTH" = "" ]; then if [ "$API_USER_ID" != "" ]; then OCI_CLI_AUTH="api_key" else if [ "$SECURITY_TOKEN_KEY_FILE" != "" ]; then OCI_CLI_AUTH="security_token" else if [ "$OCI_DELEGATION_TOKEN_FILE" != "" ]; then OCI_CLI_AUTH="instance_obo_user" else OCI_CLI_AUTH="instance_principal" # resource_principal fi fi fi fi # Check if all needed parameters are set for authorization type case "$OCI_CLI_AUTH" in api_key) if [ "$API_USER_ID" = "" -o "$API_USER_NAME" = "" -o "$API_KEY_FILE" = "" -o "$API_KEY_FINGERPRINT" = "" ]; then errstr="Auth type 'api_key' needs variables 'API_USER_ID', 'API_USER_NAME', 'API_KEY_FILE' and 'API_KEY_FINGERPRINT' set." else if [ -s "$API_KEY_FILE" ]; then encrypted=`grep "ENCRYPTED" "$API_KEY_FILE"` if [ "$encrypted" != "" -a ! -s "$passwdfile" ]; then errstr="Key file '$API_KEY_FILE' is encrypted but file '$passwdfile' is empty." else # Using api_key seems to be ok OCI_AUTH_TYPE="api_key" stat=0 fi else errstr="Key file '$API_KEY_FILE' is empty." fi fi ;; security_token) if [ "$SECURITY_TOKEN_FILE" = "" -o "$SECURITY_TOKEN_KEY_FILE" = "" ]; then errstr="Auth type 'security_token' needs variables 'SECURITY_TOKEN_FILE' and 'SECURITY_TOKEN_KEY_FILE' set." else if [ -s "$SECURITY_TOKEN_FILE" -a -s "$SECURITY_TOKEN_KEY_FILE" ]; then encrypted=`grep "ENCRYPTED" "$SECURITY_TOKEN_KEY_FILE"` if [ "$encrypted" != "" ]; then errstr="Key file '$SECURITY_TOKEN_KEY_FILE' is encrypted." else # Using security_token seems to be ok OCI_AUTH_TYPE="security_token" stat=0 fi else errstr="Key file '$SECURITY_TOKEN_KEY_FILE' or token file '$SECURITY_TOKEN_FILE' is empty." fi fi ;; instance_obo_user) if [ "$OCI_DELEGATION_TOKEN_FILE" != "" -a -s "$OCI_DELEGATION_TOKEN_FILE" ]; then # Using instance_obo_user seems to be ok OCI_AUTH_TYPE="instance_principal" stat=0 fi ;; instance_principal | resource_principal) OCI_AUTH_TYPE="instance_principal" stat=0 ;; *) errstr="Unknown CLI Auth Type '$OCI_CLI_AUTH'." esac if [ "$OCI_AUTH_TYPE" = "instance_principal" ]; then if [ -s "$instancefile" ]; then if [ "$INSTANCE_ID" = "" -o "$INSTANCE_KEY_FINGERPRINT" = "" ]; then source "$instancefile" fi fi # Check if we have to get a new token iid=`InstanceInfo "instance/id"` fp=`GetInstanceFingerprint` if [ "$iid" != "$INSTANCE_ID" -o "$fp" != "$INSTANCE_KEY_FINGERPRINT" ]; then # Get identity metadata and create temp files InstanceInfo "identity/cert.pem" | grep . > $instancecert InstanceInfo "identity/intermediate.pem" | grep . > $instanceinter InstanceInfo "identity/key.pem" | grep . > $instancekey chmod 600 $instancecert $instanceinter $instancekey tenancy=`InstanceInfo "instance/tenantId"` region=`InstanceInfo "instance/region"` realm=`InstanceInfo "instance/regionInfo/realmDomainComponent"` avd=`InstanceInfo "instance/availabilityDomain"` cid=`InstanceInfo "instance/compartmentId"` printf '# Instance configuration\n' > "$instancefile" printf 'export INSTANCE_ID="%s"\n' "$iid" >> "$instancefile" printf 'export INSTANCE_TENANT_ID="%s"\n' "$tenancy" >> "$instancefile" printf 'export INSTANCE_REGION="%s"\n' "$region" >> "$instancefile" printf 'export INSTANCE_REALM="%s"\n' "$realm" >> "$instancefile" printf 'export INSTANCE_AV_DOMAIN="%s"\n' "$avd" >> "$instancefile" printf 'export INSTANCE_COMPARTMENT_ID="%s"\n' "$cid" >> "$instancefile" printf 'export INSTANCE_TOKEN_FILE="%s"\n' "$instancetoken" >> "$instancefile" printf 'export INSTANCE_CERT_FILE="%s"\n' "$instancecert" >> "$instancefile" printf 'export INSTANCE_INTERMEDIATE_FILE="%s"\n' "$instanceinter" >> "$instancefile" printf 'export INSTANCE_KEY_FILE="%s"\n' "$instancekey" >> "$instancefile" printf 'export INSTANCE_KEY_FINGERPRINT="%s"\n' "$fp" >> "$instancefile" chmod 600 "$instancefile" source "$instancefile" GetToken stat=$? # echo "New Keyfile created." >> /tmp/result fi fi if [ $stat -eq 0 ]; then # Check for environment variables if [ "$OCI_TENANCY" = "" ]; then OCI_TENANCY=`InstanceInfo "instance/tenantId"` fi if [ "$OCI_REGION" = "" ]; then OCI_REGION=`InstanceInfo "instance/region"` fi if [ "$OCI_DEFAULT_REALM" = "" ]; then OCI_DEFAULT_REALM=`InstanceInfo "instance/regionInfo/realmDomainComponent"` fi if [ "$OCI_TENANCY_NAME" = "" -o "$OCI_HOME_REGION_KEY" = "" ]; then UseToken "identity.${OCI_REGION}.${OCI_DEFAULT_REALM}" "get" "/20160918/tenancies/$OCI_TENANCY" stat=$? if [ $stat -eq 0 -a -s $scratchfile ]; then OCI_TENANCY_NAME=`$jq -r '.name' $scratchfile` OCI_HOME_REGION_KEY=`$jq -r '.homeRegionKey' $scratchfile` fi fi if [ "$OCI_HOME_REGION" = "" ]; then UseToken "identity.${OCI_REGION}.${OCI_DEFAULT_REALM}" "get" "/20160918/regions" stat=$? if [ $stat -eq 0 -a -s $scratchfile ]; then OCI_HOME_REGION=`$jq -r '.[] | select(.key=="FRA") | .name' $scratchfile` fi fi if [ "$OCI_NAMESPACE" = "" ]; then UseToken "objectstorage.${OCI_REGION}.${OCI_DEFAULT_REALM}" "get" "/n/" stat=$? if [ $stat -eq 0 -a -s $scratchfile ]; then OCI_NAMESPACE=`$jq -r '.' $scratchfile` fi fi # Create config file with default variables, it it doesn't exist yet if [ ! -s "$configfile" ]; then printf '# OCI-API configuration\n' > "$configfile" printf 'export OCI_TENANCY="%s"\n' "$OCI_TENANCY" >> "$configfile" printf 'export OCI_TENANCY_NAME="%s"\n' "$OCI_TENANCY_NAME" >> "$configfile" printf 'export OCI_HOME_REGION="%s"\n' "$OCI_HOME_REGION" >> "$configfile" printf 'export OCI_HOME_REGION_KEY="%s"\n' "$OCI_HOME_REGION_KEY" >> "$configfile" printf 'export OCI_DEFAULT_REALM="%s"\n' "$OCI_DEFAULT_REALM" >> "$configfile" printf 'export OCI_NAMESPACE="%s"\n' "$OCI_NAMESPACE" >> "$configfile" printf 'export OCI_REGION="%s"\n' "$OCI_REGION" >> "$configfile" chmod 600 "$configfile" fi fi if [ "$DEBUG_API" = true ]; then ShowVariable "OCI_AUTH_TYPE" "$OCI_AUTH_TYPE" ShowVariable "OCI_CLI_AUTH" "$OCI_CLI_AUTH" ShowVariable "OCI_TENANCY" "$OCI_TENANCY" ShowVariable "OCI_REGION" "$OCI_REGION" ShowVariable "OCI_DEFAULT_REALM" "$OCI_DEFAULT_REALM" ShowVariable "errstr" "$errstr" fi return $stat } # url encode all special characters except "/", "?", "=", and "&" function rawurlencode { local string="${1}" local strlen=${#string} local encoded="" local pos c o for (( pos=0 ; pos ${scratchfile}.head # Check header for HTTP return code code=`grep "^HTTP/" ${scratchfile}.head` result=`echo "$code" | cut -d' ' -f2` if [ "$result" != "200" ]; then stat=102 else # Grep number of total items items=`grep "^opc-total-items: " ${scratchfile}.head | cut -d' ' -f2-` # Grep transfer method transfer=`grep "^Transfer-Encoding: " ${scratchfile}.head | cut -d' ' -f2-` # Check header for following pages # opc-retry-token and opc-request-id maybe also interesting here np=`grep "^opc-next-page: " ${scratchfile}.head | cut -d' ' -f2-` # Check header for previous pages pp=`grep "^opc-previous-page: " ${scratchfile}.head | cut -d' ' -f2-` # We are also interested in body if not only showhead if [ "$showhead" = false ]; then result=`filecheck -s "$scratchfile"` if [ "$result" != "" ]; then result=`head -c 1 $scratchfile` if [ "$result" = "[" ]; then havearray=true else havearray=false # Check for sub-arrays in JSON conversion="" case "$service" in query) result=`head -n 10 $scratchfile | grep '^ "items": '` if [ "$result" != "" ]; then conversion="items" fi ;; idcs) result=`head -n 10 $scratchfile | grep '^ "Resources": '` if [ "$result" != "" ]; then conversion="Resources" fi ;; objectstorage) result=`head -n 10 $scratchfile | grep '^ "objects": '` if [ "$result" != "" ]; then conversion="objects" fi ;; esac fi if [ "$service" = "objectstorage" ]; then # Check for errors or next start (objectstorage obejcts) in output ns=`browse-json nextStartWith --select 1 --raw --quiet --import $scratchfile` if [ "$ns" != "" ]; then ns=`rawurlencode $ns` fi fi fi fi fi else # Unfortunately, we don't have a header file (this should be unusual) - check output instead if [ "$showhead" = false ]; then result=`filecheck -s "$scratchfile"` if [ "$result" != "" ]; then code=`browse-json code --select 1 --raw --quiet --import $scratchfile` if [ "$code" = "" ]; then stat=103 fi else stat=104 fi fi fi return $stat } function PrinttHeaderObject { local name="${1}" local size="" local storageTier="" local archivalState="" local md5="" local versionID="" local virtualFolder="" local virtualFolderCreator="" local etag="" local date="" local lastModified="" printf '{' if [ -r ${scratchfile}.head -a "$name" != "" ]; then size=`grep '^Content-Length: ' ${scratchfile}.head | cut -d' ' -f2-` storageTier=`grep '^storage-tier: ' ${scratchfile}.head | cut -d' ' -f2-` archivalState=`grep '^archival-state: ' ${scratchfile}.head | cut -d' ' -f2-` md5=`grep '^content-md5: ' ${scratchfile}.head | cut -d' ' -f2-` versionID=`grep '^version-id: ' ${scratchfile}.head | cut -d' ' -f2-` virtualFolder=`grep '^opc-meta-virtual-folder-directory-object: ' ${scratchfile}.head | cut -d' ' -f2-` virtualFolderCreator=`grep '^opc-meta-virtual-folder-directory-createdby: ' ${scratchfile}.head | cut -d' ' -f2-` etag=`grep '^etag: ' ${scratchfile}.head | cut -d' ' -f2-` date=`grep '^date: ' ${scratchfile}.head | cut -d' ' -f2-` lastModified=`grep '^last-modified: ' ${scratchfile}.head | cut -d' ' -f2-` if [ "$archivalState" = "" ]; then archivalState=null else archivalState='"'$archivalState'"' fi if [ "$virtualFolder" = "" ]; then virtualFolder=null else virtualFolder="$virtualFolder" fi if [ "$virtualFolderCreator" = "" ]; then virtualFolderCreator=null else virtualFolderCreator='"'$virtualFolderCreator'"' fi printf '\n "name": "%s",\n "size": %s,\n "storageTier": "%s",\n "archivalState": %s,\n "md5": "%s",\n "versionID": "%s",\n "virtualFolder": %s,\n "virtualFolderCreator": %s,\n "etag": "%s",\n "date": "%s",\n "lastModified": "%s"\n' "$name" "$size" "$storageTier" "$archivalState" "$md5" "$versionID" "$virtualFolder" "$virtualFolderCreator" "$etag" "$date" "$lastModified" fi printf '}\n' } function PrintHeader { echo '{' printf ' "code": "%s",\n' "$code" if [ "$items" != "" ]; then printf ' "opcTotalItems": %s,\n' "$items" else printf ' "opcTotalItems": null,\n' fi if [ "$pp" != "" ]; then printf ' "opcPreviousPage": "%s",\n' "$pp" else printf ' "opcPreviousPage": null,\n' fi if [ "$np" != "" ]; then printf ' "opcNextPage": "%s",\n' "$np" else printf ' "opcNextPage": null,\n' fi if [ "$transfer" != "" ]; then printf ' "transferEncoding": "%s",\n' "$transfer" else printf ' "transferEncoding": null,\n' fi printf ' "service": "%s"\n' "$service" echo '}' } ### Main items=0 # Number of total items from header titems=0 # Number of total items calculated from array showhead=false # true for only display http head data (converted to JSON) havearray=false # true if JSON response starting with an array '[' haveOSObject="" # Name of the object in objectstorage we try to download or get metadata of filename="" # Needed for post and put conversion="" # Could be "items", "Resources" or "objects" endpoint="" # Rest API Endpoint (URL) method="" # get / delete / head / post / put request="" # Version and Call service="" # OCI service code="" # Status of the call e.g. "HTTP/1.1 200 OK" np="" # Next page (oagination for long lists) pp="" # Previous page (oagination for long lists) ns="" # Next start (objectstorage objects pagination) # Check parameters: Loop until all parameters are used up while [ $# -gt 0 ]; do pname=${1} case "$pname" in -v | --version) shift showversion=true ;; -h | --help) shift showhelp=true ;; *) shift paramck=`echo "$pname" | grep '^-'` # Keys don't begin with '-' if [ "$paramck" != "" ]; then errstr="Unknown option '$pname'." else if [ "$endpoint" = "" ]; then endpoint=`echo "$pname" | tolower | sed 's|^https://||'` else if [ "$method" = "" ]; then method=`echo "$pname" | tolower` else if [ "$request" = "" ]; then if [ "$method" = "post" -o "$method" = "put" ]; then if [ "$filename" = "" ]; then filename="$pname" if [ ! -r "$filename" ]; then errstr="Unable to read file '$filename'." fi if [ "$1" != "" ]; then request=${1} shift else errstr="Please specify a filename after method '$method' and before your request." fi else errstr="Unknown additional parameter: '$pname'." fi else request="$pname" fi else errstr="Unknown additional parameter: '$pname'." fi fi fi fi esac done # Plausibility check if [ "$endpoint" = "" -o "$method" = "" -o "$request" = "" ]; then errstr="Unsufficient parameters were specified." else # Determine service from endpoint and check if it is objectstorage request is looking for objects result=`echo "$endpoint" | grep ".compat.objectstorage."` if [ "$result" != "" ]; then service="compat-objectstorage" else result=`echo "$endpoint" | grep "^idcs-"` if [ "$result" != "" ]; then service="idcs" else service=`echo "$endpoint" | cut -d'.' -f1` if [ "$service" = "objectstorage" ]; then haveOSObject=`echo "$request" | grep '/o/' | sed 's|/o/|:|' | cut -d':' -f2` fi fi fi fi # Display help or error message DisplayHelp # Check if openssl is installed openssl=`filecheck -x openssl` if [ "$openssl" = "" ]; then exitcode=100 errormsg 0 "($progstr) 'openssl' not installed. Please install first." "Error: $exitcode" exit $exitcode fi # Check if curl and jq is installed jq=`filecheck -x jq` curl=`filecheck -x curl` if [ "$curl" = "" -o "$jq" = "" ]; then exitcode=100 errormsg 0 "($progstr) 'curl' or 'jq' not installed. Please run 'setup-tools update'." "Error: $exitcode" exit $exitcode fi # Check if api is configurered - if not, try to get as much infos as possible CheckAPIConfig stat=$? if [ $stat -ne 0 ]; then exitcode=101 errormsg 0 "($progstr) $errstr" "Error: $exitcode" # Cleanup exit $exitcode fi # Call the REST API if [ "$service" = "compat-objectstorage" ]; then if [ "$S3_ACCESSKEY" != "" -a "$S3_SECRETKEY" != "" ]; then s3-curl "$endpoint" "$method" "$request" exitcode=$? else exitcode=101 fi else # echo "OCI_CLI_AUTH: '$OCI_CLI_AUTH'." >> /tmp/result oci-curl "$endpoint" "$method" $filename "$request" exitcode=$? fi if [ "$DEBUG_API" = true ]; then printf "\nexitcode curl: '%s'.\n" "$exitcode" printf "service: '%s'.\n" "$service" printf "endpoint: '%s'.\n" "$endpoint" printf "method: '%s'.\n" "$method" printf "filename: '%s'.\n" "$filename" printf "request: '%s'.\n" "$request" printf "haveOSObject: '%s'.\n" "$haveOSObject" if [ $exitcode -eq 0 ]; then # Check, if we only wanted to see the header result=`diff $scratchfile ${scratchfile}.head` stat=$? if [ $stat -eq 0 ]; then # We only asked for header information - no JSON in output showhead=true else # Check JSON syntax and pp JSON mv -f $scratchfile ${scratchfile}.pre cat ${scratchfile}.pre | "$jq" > $scratchfile exitcode=$? printf "jq exitcode: '%s'.\n\n" "$exitcode" read -p "Press enter to continue" printf "\nUnmodified Body:\n" cat ${scratchfile}.pre | more fi if [ $exitcode -eq 0 ]; then checkfiles exitcode=$? printf "checkfiles exitcode: '%s'.\n" "$exitcode" printf "code: '%s'.\n" "$code" printf "havearray: '%s'.\n" "$havearray" printf "conversion: '%s'.\n" "$conversion" printf "transfer: '%s'.\n" "$transfer" printf "items: '%s'.\n" "$items" printf "np: '%s'.\n" "$np" printf "pp: '%s'.\n" "$pp" printf "ns: '%s'.\n" "$ns" if [ "$showhead" = true ]; then printf "\nHead:\n" cat ${scratchfile}.head else printf "\n" read -p "Press enter to continue" printf "\nHead:\n" cat ${scratchfile}.head read -p "Press enter to continue" printf "\nBody:\n" cat $scratchfile | more fi fi fi Cleanup exit fi # Check if curl succeeded and output is valid JSON if [ $exitcode -gt 0 ]; then errormsg 0 "($progstr) Unable to access REST API. Error code: $exitcode." else result=`diff $scratchfile ${scratchfile}.head` stat=$? if [ $stat -eq 0 ]; then # We only asked for header information - no JSON in output showhead=true else if [ "$haveOSObject" != "" -a "$method" = "get" ]; then # Assuming we want to download of a file from objectstorage checkfiles exitcode=$? if [ $exitcode -eq 0 ]; then cat $scratchfile fi Cleanup exit $exitcode fi # Check JSON syntax and pp JSON mv -f $scratchfile ${scratchfile}.pre cat ${scratchfile}.pre | "$jq" '.' > $scratchfile exitcode=$? fi if [ $exitcode -eq 0 ]; then checkfiles exitcode=$? fi # if [ $exitcode -eq 0 -o "$showhead" = true ]; then if [ $exitcode -eq 0 ]; then if [ $showhead = true ]; then # Just one page - don't loop over all pages np="" ns="" if [ "$haveOSObject" != "" ]; then # Get metadata for an OS object PrinttHeaderObject "$haveOSObject" > ${scratchfile}.out else PrintHeader > ${scratchfile}.out fi else # We had a result if [ "$conversion" = "" ]; then mv -f $scratchfile ${scratchfile}.out else cat $scratchfile | "$jq" '.'$conversion > ${scratchfile}.out fi fi # We have more than one page while [ "$np" != "" -o "$ns" != "" ]; do oci-curl "$endpoint" "$method" $filename "$request" # --connect-timeout 10 --max-time 60 --retry 3 --retry-delay 5 -D ${scratchfile}.head | "$jq" '.' > $scratchfile 2>/dev/null exitcode=$? if [ $exitcode -eq 0 ]; then checkfiles exitcode=$? else np="" ns="" fi if [ $exitcode -eq 0 ]; then if [ "$conversion" = "" ]; then cat $scratchfile >> ${scratchfile}.out else cat $scratchfile | jq '.'$conversion >> ${scratchfile}.out fi fi done # Check for empty result result=`head -n 1 ${scratchfile}.out` if [ "$result" != '[]' ]; then result=`echo "$result" | grep '"'` if [ "$result" != "" ]; then # Result is a string printf '[\n %s\n]\n' "$result" > ${scratchfile}.pre else # Normalize JSON cat ${scratchfile}.out | norm-json --quiet --raw > ${scratchfile}.pre fi if [ "$ENVELOPE_API" = false ]; then mv -f ${scratchfile}.pre ${scratchfile}.out else titems=`cat ${scratchfile}.pre | "$jq" -r '. | length'` printf '{\n "content": ' > ${scratchfile}.out if [ $titems -eq 1 ]; then cat ${scratchfile}.pre | "$jq" '.[]' >> ${scratchfile}.out else cat ${scratchfile}.pre >> ${scratchfile}.out fi printf ',\n "contentItems": %s,\n "creator": "oci-api",\n "service": "%s",\n "endpoint": "%s",\n "method": "%s",\n "request": "%s"\n}\n' \ "$titems" "$service" "$endpoint" "$method" "$request" >> ${scratchfile}.out fi cat ${scratchfile}.out | "$jq" -M fi fi fi # Remove temp files Cleanup # Exit with error code exit $exitcode