Curl-only test suite for validating RSA API key authentication against two OCI identity domains from a shell. No OCI CLI required. Built for SRE smoke-testing of external integrations — Splunk, Wiz.io, Terraform Enterprise. Includes cross-domain user management scripts.
Two identity domains, two authentication models — both using OCI API Key Signature v1 (RSA-SHA256), but against
different base URLs. The DEFAULT domain is a legacy IAM user hitting /20160918/.
The DEVEL domain is an IDCS user hitting /admin/v1/ SCIM endpoints.
No OAuth tokens involved anywhere.
Test Suite run_tests.sh (orchestrator) ├── test_default_domain.sh 9 tests — legacy IAM /20160918/ endpoints ├── test_devel_domain.sh 9 tests — IDCS /admin/v1/ SCIM endpoints └── test_cross_domain.sh 8 tests — isolation & forgery negative tests Shared Libraries ├── lib_oci_sign.sh OCI Signature v1 (RSA-SHA256) + wrappers ├── lib_test_helpers.sh Colored output, counters, summary └── env.sh All credentials, endpoints & domain OCIDs Login / Smoke-test Scripts ├── login_default.sh Verify DEFAULT domain auth; export env vars └── login_devel.sh Verify DEVEL domain auth via IDCS /admin/v1/Me Cross-Domain User Creation ├── create_crosstest_user.sh <username> DEFAULT user → creates in DEVEL IDCS └── create_crosstest_in_default.sh <username> DEVEL user → creates in DEFAULT IDCS
| Domain | Auth Model | Endpoint |
|---|---|---|
| Default | Legacy IAM user | identity.<region>.oraclecloud.com/20160918/ |
| Devel | IDCS user (OCI Identity Domains) | idcs-<guid>.identity.oraclecloud.com/admin/v1/ |
| Prerequisite | Purpose |
|---|---|
| curl | HTTP requests to OCI REST APIs |
| openssl | RSA-SHA256 signing of each request |
| base64 | Encoding the signature |
| python3 | JSON parsing in test assertions |
./run_tests.sh — run all 26 tests across both domains
./run_tests.sh default — default domain only
./run_tests.sh devel — devel domain (IDCS) only
./run_tests.sh cross — isolation tests only
./login_default.sh — verify DEFAULT domain, print identity
./login_devel.sh — verify DEVEL domain via IDCS, print identity
source ./login_default.sh — same, but also export env vars into shell
./create_crosstest_user.sh <username> — DEFAULT user creates in DEVEL
./create_crosstest_in_default.sh <username> — DEVEL user creates in DEFAULT
Email is derived as <username>@ryzior.com. Acting identity is displayed before action.
Central configuration. Holds credentials, IDCS URLs and domain OCIDs for both domains. Note the two distinct auth endpoints — legacy IAM for Default, IDCS SCIM for Devel.
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# OCI Auth Test Suite — Environment Configuration
#
# Two identity domains, two authentication models:
#
# DEFAULT domain → legacy IAM user, RSA-signed /20160918/ API
# DEVEL domain → IDCS user, RSA-signed /admin/v1/ SCIM API
#
# Both use OCI API Key Signature v1 (RSA-SHA256), but against
# different base URLs.
# ─────────────────────────────────────────────────────────────────
# ── Region ──
export OCI_REGION="eu-frankfurt-1"
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# DEFAULT DOMAIN (Legacy IAM)
# Auth target: DEFAULT_BASE_URL/20160918/
# User lives in legacy IAM — not visible via IDCS SCIM API.
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
export DEFAULT_TENANCY_OCID="ocid1.tenancy.oc1..aaaaaaaafwvqjti6nzubd3yf6obb2amwmr5c43j72n65gbbfhumrq2wf4f2a"
export DEFAULT_USER_OCID="ocid1.user.oc1..aaaaaaaaxxagu32ikzlgsylotiogywsr7wwa3redkrcfihwcxhke7uuqytuq"
export DEFAULT_FINGERPRINT="c8:ac:83:36:50:13:45:17:e9:bb:2a:41:66:93:57:fb"
export DEFAULT_KEY_PATH="${HOME}/.oci/oci_api_key.pem"
export DEFAULT_BASE_URL="https://identity.${OCI_REGION}.oraclecloud.com"
export DEFAULT_DOMAIN_OCID="ocid1.domain.oc1..aaaaaaaa5v6cgj5ywxg7ycuiexkwa5zodstgyobb7afadvsdr7umkp6nbzfa"
export DEFAULT_IDCS_URL="https://idcs-4a915662984e434495247863514dc115.identity.oraclecloud.com"
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# DEVEL IDENTITY DOMAIN (OCI Identity Domains / IDCS)
# Auth target: DEVEL_IDCS_URL/admin/v1/ (SCIM API)
# User lives in IDCS — NOT accessible via legacy /20160918/ API.
# Same RSA-SHA256 signing mechanism, different host in signing string.
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
export DEVEL_TENANCY_OCID="ocid1.tenancy.oc1..aaaaaaaafwvqjti6nzubd3yf6obb2amwmr5c43j72n65gbbfhumrq2wf4f2a"
export DEVEL_USER_OCID="ocid1.user.oc1..aaaaaaaanwjvwebk3xk4b5fnhyxk5skhy6oesspvtftll6uxftmcbmbrxuuq"
export DEVEL_FINGERPRINT="0a:e3:74:04:8a:66:bd:b7:bc:d2:77:64:ee:eb:80:80"
export DEVEL_KEY_PATH="${HOME}/.oci/ryzior_oci_api_key.pem"
export DEVEL_BASE_URL="https://identity.${OCI_REGION}.oraclecloud.com"
export DEVEL_DOMAIN_OCID="ocid1.domain.oc1..aaaaaaaajmqq63n33q5msygn6pqlxopaoxvplec6vscf4xhkr67wjkyxqtda"
export DEVEL_IDCS_URL="https://idcs-a8490e61856a4816858a942ef5289a6c.identity.oraclecloud.com"
RSA-SHA256 signing engine. Builds the OCI Signature v1 Authorization header for every request.
default_curl() targets the legacy IAM endpoint; devel_curl() targets the IDCS endpoint.
The signing algorithm is identical — only the host in the signing string differs.
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# OCI API Signature v1 — Shared Signing Library
#
# Provides oci_signed_curl() which handles RSA-SHA256 request
# signing for any OCI REST API call.
#
# Usage:
# source ./lib_oci_sign.sh
# oci_signed_curl <METHOD> <FULL_URL> <TENANCY_OCID> <USER_OCID> \
# <FINGERPRINT> <KEY_PATH> [REQUEST_BODY]
# ─────────────────────────────────────────────────────────────────
oci_signed_curl() {
local METHOD="$1"
local FULL_URL="$2"
local TENANCY_OCID="$3"
local USER_OCID="$4"
local FINGERPRINT="$5"
local KEY_PATH="$6"
local BODY="${7:-}"
# ── Validate key file exists ──
if [[ ! -f "$KEY_PATH" ]]; then
echo "{\"error\": \"Private key not found: ${KEY_PATH}\"}" >&2
return 1
fi
# ── Parse URL components ──
local HOST
HOST=$(echo "$FULL_URL" | sed 's|https://||' | cut -d'/' -f1)
local REQUEST_TARGET
REQUEST_TARGET=$(echo "$FULL_URL" | sed "s|https://${HOST}||")
# ── Date header (must be RFC 2616) ──
local DATE
DATE=$(date -u '+%a, %d %b %Y %H:%M:%S GMT')
# ── Key ID = tenancy/user/fingerprint ──
local KEY_ID="${TENANCY_OCID}/${USER_OCID}/${FINGERPRINT}"
# ── Method must be lowercase in signing string ──
local METHOD_LOWER
METHOD_LOWER=$(echo "$METHOD" | tr '[:upper:]' '[:lower:]')
# ── Build signing string & headers list ──
local SIGNING_STRING="(request-target): ${METHOD_LOWER} ${REQUEST_TARGET}
date: ${DATE}
host: ${HOST}"
local HEADERS_LIST="(request-target) date host"
# ── Extra headers for POST/PUT/PATCH with a body ──
local CONTENT_TYPE=""
local CONTENT_LENGTH=""
local CONTENT_SHA=""
if [[ -n "$BODY" ]]; then
CONTENT_TYPE="application/json"
CONTENT_LENGTH="${#BODY}"
CONTENT_SHA=$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | base64)
SIGNING_STRING="${SIGNING_STRING}
content-length: ${CONTENT_LENGTH}
content-type: ${CONTENT_TYPE}
x-content-sha256: ${CONTENT_SHA}"
HEADERS_LIST="${HEADERS_LIST} content-length content-type x-content-sha256"
fi
# ── RSA-SHA256 signature ──
local SIGNATURE
SIGNATURE=$(printf '%s' "$SIGNING_STRING" \
| openssl dgst -sha256 -sign "$KEY_PATH" \
| base64 \
| tr -d '\n')
local AUTH_HEADER="Signature version=\"1\",keyId=\"${KEY_ID}\",algorithm=\"rsa-sha256\",headers=\"${HEADERS_LIST}\",signature=\"${SIGNATURE}\""
# ── Execute curl ──
if [[ -n "$BODY" ]]; then
curl -s -w "\n%{http_code}" -X "${METHOD}" "${FULL_URL}" \
-H "date: ${DATE}" \
-H "host: ${HOST}" \
-H "content-type: ${CONTENT_TYPE}" \
-H "content-length: ${CONTENT_LENGTH}" \
-H "x-content-sha256: ${CONTENT_SHA}" \
-H "Authorization: ${AUTH_HEADER}" \
-d "$BODY"
else
curl -s -w "\n%{http_code}" -X "${METHOD}" "${FULL_URL}" \
-H "date: ${DATE}" \
-H "host: ${HOST}" \
-H "Authorization: ${AUTH_HEADER}"
fi
}
# ─────────────────────────────────────────────────────────────────
# Convenience wrappers that pick the right credential set
#
# DEFAULT domain user authenticates against the legacy IAM API:
# https://identity.<region>.oraclecloud.com/20160918/
#
# DEVEL domain user authenticates against the IDCS SCIM API:
# https://idcs-<guid>.identity.oraclecloud.com/admin/v1/
#
# The signing algorithm is identical for both — only the host in
# the signing string and Authorization header differs.
# ─────────────────────────────────────────────────────────────────
# Signs with DEFAULT domain credentials (legacy IAM user)
default_curl() {
local METHOD="$1"; shift
local URL="$1"; shift
local BODY="${1:-}"
oci_signed_curl "$METHOD" "$URL" \
"$DEFAULT_TENANCY_OCID" "$DEFAULT_USER_OCID" \
"$DEFAULT_FINGERPRINT" "$DEFAULT_KEY_PATH" \
"$BODY"
}
# Signs with DEVEL domain credentials (IDCS user)
# Use with DEVEL_IDCS_URL/admin/v1/ endpoints — not /20160918/
devel_curl() {
local METHOD="$1"; shift
local URL="$1"; shift
local BODY="${1:-}"
oci_signed_curl "$METHOD" "$URL" \
"$DEVEL_TENANCY_OCID" "$DEVEL_USER_OCID" \
"$DEVEL_FINGERPRINT" "$DEVEL_KEY_PATH" \
"$BODY"
}
# ─────────────────────────────────────────────────────────────────
# Helper: split curl output into body + http_code
# Usage: read_response "$(some_curl_call)" BODY CODE
# ─────────────────────────────────────────────────────────────────
read_response() {
local RAW="$1"
local -n _BODY=$2
local -n _CODE=$3
_CODE=$(echo "$RAW" | tail -1)
_BODY=$(echo "$RAW" | sed '$d')
}
Colored terminal output, test counters, and the final summary printer. Tracks PASS / FAIL / WARN / SKIP across all sourced suites.
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Test Helper Library — colored output, counters, formatting
# ─────────────────────────────────────────────────────────────────
# Colors
_RED='\033[0;31m'
_GREEN='\033[0;32m'
_YELLOW='\033[0;33m'
_CYAN='\033[0;36m'
_BOLD='\033[1m'
_NC='\033[0m'
# Counters (exported so they accumulate across sourced scripts)
export TESTS_PASS=${TESTS_PASS:-0}
export TESTS_FAIL=${TESTS_FAIL:-0}
export TESTS_WARN=${TESTS_WARN:-0}
export TESTS_SKIP=${TESTS_SKIP:-0}
export TESTS_TOTAL=${TESTS_TOTAL:-0}
section() {
echo ""
echo -e "${_BOLD}${_CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_NC}"
echo -e "${_BOLD}${_CYAN} $1${_NC}"
echo -e "${_BOLD}${_CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_NC}"
}
test_start() {
TESTS_TOTAL=$((TESTS_TOTAL + 1))
echo ""
echo -e " ${_BOLD}▶ $1${_NC}"
}
pass() {
TESTS_PASS=$((TESTS_PASS + 1))
echo -e " ${_GREEN}[PASS]${_NC} $1"
}
fail() {
TESTS_FAIL=$((TESTS_FAIL + 1))
echo -e " ${_RED}[FAIL]${_NC} $1"
}
warn() {
TESTS_WARN=$((TESTS_WARN + 1))
echo -e " ${_YELLOW}[WARN]${_NC} $1"
}
skip() {
TESTS_SKIP=$((TESTS_SKIP + 1))
echo -e " ${_YELLOW}[SKIP]${_NC} $1"
}
detail() {
# Print truncated response body for debugging
local MSG="$1"
local MAX_LEN=300
if [[ ${#MSG} -gt $MAX_LEN ]]; then
MSG="${MSG:0:$MAX_LEN}... (truncated)"
fi
echo -e " ${_CYAN}→ ${MSG}${_NC}"
}
print_summary() {
echo ""
echo -e "${_BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_NC}"
echo -e "${_BOLD} TEST SUMMARY${_NC}"
echo -e "${_BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_NC}"
echo -e " Total: ${TESTS_TOTAL}"
echo -e " ${_GREEN}Passed: ${TESTS_PASS}${_NC}"
echo -e " ${_RED}Failed: ${TESTS_FAIL}${_NC}"
echo -e " ${_YELLOW}Warnings: ${TESTS_WARN}${_NC}"
echo -e " ${_YELLOW}Skipped: ${TESTS_SKIP}${_NC}"
echo ""
if [[ "$TESTS_FAIL" -gt 0 ]]; then
echo -e " ${_RED}${_BOLD}RESULT: FAILURES DETECTED${_NC}"
return 1
elif [[ "$TESTS_WARN" -gt 0 ]]; then
echo -e " ${_YELLOW}${_BOLD}RESULT: PASSED WITH WARNINGS${_NC}"
return 0
else
echo -e " ${_GREEN}${_BOLD}RESULT: ALL TESTS PASSED${_NC}"
return 0
fi
}
Main entry point. Validates prerequisites, sources credentials, prints the domain/endpoint summary, runs selected suites, and exits 0 on success or 1 if any FAIL was recorded.
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# OCI Identity Auth Test Runner
#
# Tests RSA-signed API key authentication for:
# 1. Default domain (legacy IAM)
# 2. Devel identity domain
# 3. Cross-domain isolation (negative tests)
#
# Usage:
# ./run_tests.sh # run all suites
# ./run_tests.sh default # run only default domain tests
# ./run_tests.sh devel # run only devel domain tests
# ./run_tests.sh cross # run only cross-domain tests
# ./run_tests.sh default devel # run specific suites
# ─────────────────────────────────────────────────────────────────
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ── Pre-flight checks ──
echo ""
echo "╔═══════════════════════════════════════════════════╗"
echo "║ OCI Identity Domain — Auth Test Suite ║"
echo "║ RSA API Key Signing (Signature v1) ║"
echo "╚═══════════════════════════════════════════════════╝"
echo ""
echo "Timestamp : $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "Hostname : $(hostname)"
echo ""
# ── Validate prerequisites ──
MISSING=0
for CMD in curl openssl python3 base64; do
if ! command -v "$CMD" &>/dev/null; then
echo "ERROR: Required command '${CMD}' not found in PATH."
MISSING=1
fi
done
[[ "$MISSING" -eq 1 ]] && exit 1
# ── Source env — abort early if not configured ──
if [[ ! -f "${SCRIPT_DIR}/env.sh" ]]; then
echo "ERROR: env.sh not found. Copy env.sh.example and fill in your values."
exit 1
fi
source "${SCRIPT_DIR}/env.sh"
# ── Validate key files exist ──
for LABEL_KEY in "DEFAULT:${DEFAULT_KEY_PATH}" "DEVEL:${DEVEL_KEY_PATH}"; do
LABEL="${LABEL_KEY%%:*}"
KPATH="${LABEL_KEY#*:}"
if [[ ! -f "$KPATH" ]]; then
echo "ERROR: ${LABEL} private key not found: ${KPATH}"
MISSING=1
fi
done
[[ "$MISSING" -eq 1 ]] && exit 1
echo "Region : ${OCI_REGION}"
echo ""
echo "Default domain (legacy IAM)"
echo " API : ${DEFAULT_BASE_URL}/20160918/"
echo " User : ${DEFAULT_USER_OCID:0:40}..."
echo " Key : ${DEFAULT_FINGERPRINT}"
echo ""
echo "Devel domain (IDCS / OCI Identity Domains)"
echo " API : ${DEVEL_IDCS_URL}/admin/v1/"
echo " User : ${DEVEL_USER_OCID:0:40}..."
echo " Key : ${DEVEL_FINGERPRINT}"
echo ""
# ── Source shared libraries ──
source "${SCRIPT_DIR}/lib_oci_sign.sh"
source "${SCRIPT_DIR}/lib_test_helpers.sh"
# ── Determine which suites to run ──
SUITES=("$@")
if [[ ${#SUITES[@]} -eq 0 ]]; then
SUITES=("default" "devel" "cross")
fi
# ── Run requested suites ──
for SUITE in "${SUITES[@]}"; do
case "$SUITE" in
default)
source "${SCRIPT_DIR}/test_default_domain.sh"
;;
devel)
source "${SCRIPT_DIR}/test_devel_domain.sh"
;;
cross)
source "${SCRIPT_DIR}/test_cross_domain.sh"
;;
*)
echo "Unknown suite: ${SUITE} (valid: default, devel, cross)"
exit 1
;;
esac
done
# ── Summary ──
print_summary
EXIT_CODE=$?
echo ""
echo "Log timestamp: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
exit $EXIT_CODE
9 tests against the legacy IAM default domain via /20160918/.
Covers user retrieval, tenancy metadata, API keys, auth tokens, groups, policies, compartments, and identity providers.
| # | Test | Validates |
|---|---|---|
| T1 | GET own user | API key auth works, user is ACTIVE |
| T2 | List users | Read access to tenancy user list |
| T3 | GET tenancy | Tenancy metadata readable |
| T4 | List API keys | Can enumerate keys for the user |
| T5 | List auth tokens | Can enumerate SWIFT/auth tokens |
| T6 | List groups | Group enumeration works |
| T7 | List policies | Policy enumeration works |
| T8 | List compartments | Compartment visibility |
| T9 | List identity providers | Federation (SAML2) config readable |
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Test Suite: DEFAULT DOMAIN (Legacy IAM)
# All calls use OCI API Key Signature v1 (RSA-SHA256)
# ─────────────────────────────────────────────────────────────────
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/env.sh"
source "${SCRIPT_DIR}/lib_oci_sign.sh"
source "${SCRIPT_DIR}/lib_test_helpers.sh"
section "DEFAULT DOMAIN — Legacy IAM"
BASE="${DEFAULT_BASE_URL}"
# ──────────────────────────────────────────────
# T1: Get the authenticated user's own details
# ──────────────────────────────────────────────
test_start "T1: GET own user details"
RAW=$(default_curl GET "${BASE}/20160918/users/${DEFAULT_USER_OCID}")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
LIFECYCLE=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('lifecycleState','UNKNOWN'))" 2>/dev/null || echo "PARSE_ERROR")
if [[ "$LIFECYCLE" == "ACTIVE" ]]; then
pass "User retrieved — lifecycleState=ACTIVE (HTTP ${CODE})"
else
warn "User retrieved but lifecycleState=${LIFECYCLE} (HTTP ${CODE})"
fi
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T2: List users in the tenancy
# ──────────────────────────────────────────────
test_start "T2: List users in tenancy"
RAW=$(default_curl GET "${BASE}/20160918/users?compartmentId=${DEFAULT_TENANCY_OCID}&limit=5")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
pass "Listed ${COUNT} user(s) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T3: Get tenancy metadata
# ──────────────────────────────────────────────
test_start "T3: GET tenancy details"
RAW=$(default_curl GET "${BASE}/20160918/tenancies/${DEFAULT_TENANCY_OCID}")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
TNAME=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('name','???'))" 2>/dev/null || echo "PARSE_ERROR")
pass "Tenancy name: ${TNAME} (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T4: List API keys for the user
# ──────────────────────────────────────────────
test_start "T4: List API keys for user"
RAW=$(default_curl GET "${BASE}/20160918/users/${DEFAULT_USER_OCID}/apiKeys/")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
KEY_COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
pass "Found ${KEY_COUNT} API key(s) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T5: List auth tokens for the user
# ──────────────────────────────────────────────
test_start "T5: List auth tokens for user"
RAW=$(default_curl GET "${BASE}/20160918/users/${DEFAULT_USER_OCID}/authTokens/")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
TOKEN_COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
pass "Found ${TOKEN_COUNT} auth token(s) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T6: List groups in the tenancy
# ──────────────────────────────────────────────
test_start "T6: List groups in tenancy"
RAW=$(default_curl GET "${BASE}/20160918/groups?compartmentId=${DEFAULT_TENANCY_OCID}&limit=5")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
GRP_COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
pass "Listed ${GRP_COUNT} group(s) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T7: List policies in the tenancy
# ──────────────────────────────────────────────
test_start "T7: List policies in tenancy"
RAW=$(default_curl GET "${BASE}/20160918/policies?compartmentId=${DEFAULT_TENANCY_OCID}&limit=5")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
POL_COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
pass "Listed ${POL_COUNT} policy(ies) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T8: List compartments
# ──────────────────────────────────────────────
test_start "T8: List compartments"
RAW=$(default_curl GET "${BASE}/20160918/compartments?compartmentId=${DEFAULT_TENANCY_OCID}&limit=5")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
COMP_COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
pass "Listed ${COMP_COUNT} compartment(s) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T9: List identity providers (federation)
# ──────────────────────────────────────────────
test_start "T9: List identity providers"
RAW=$(default_curl GET "${BASE}/20160918/identityProviders?compartmentId=${DEFAULT_TENANCY_OCID}&protocol=SAML2&limit=10")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
IDP_COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
pass "Listed ${IDP_COUNT} IdP(s) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
9 tests against the devel identity domain via IDCS /admin/v1/ SCIM endpoints.
The devel user is IDCS-only — not present in legacy IAM. T7 uses DEFAULT credentials to verify
domain metadata from the IAM control plane. T8 validates the legacy IAM security boundary.
| # | Test | Validates |
|---|---|---|
| T1 | GET own user (/admin/v1/Me) | IDCS API key auth works, user active |
| T2 | List domain users | User enumeration in devel domain |
| T3 | List domain groups | Group enumeration in devel domain |
| T4 | API key capability flag | canUseApiKeys=true on user profile |
| T5 | IDCS domain settings | Domain settings API accessible |
| T6 | List domain apps | App/OAuth client enumeration |
| T7 | GET domain metadata (default creds) | Devel domain visible from IAM control plane |
| T8 | Legacy IAM boundary check | IDCS user correctly absent from /20160918/ — or WARN if IAM policy grants access |
| T9 | IDCS OIDC discovery (unauthenticated) | IDCS layer health |
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Test Suite: DEVEL IDENTITY DOMAIN (OCI Identity Domains / IDCS)
#
# The devel domain user is an IDCS identity — NOT a legacy IAM user.
# All authenticated calls go to DEVEL_IDCS_URL/admin/v1/ (SCIM API).
# The signing algorithm is identical to legacy IAM (RSA-SHA256),
# but the host in the signing string is the IDCS endpoint, not
# identity.<region>.oraclecloud.com.
# ─────────────────────────────────────────────────────────────────
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/env.sh"
source "${SCRIPT_DIR}/lib_oci_sign.sh"
source "${SCRIPT_DIR}/lib_test_helpers.sh"
section "DEVEL IDENTITY DOMAIN — IDCS SCIM API"
IDCS="${DEVEL_IDCS_URL}"
# ──────────────────────────────────────────────
# T1: Get the authenticated user's own details
# IDCS /admin/v1/Me — equivalent of GET own user in legacy IAM
# ──────────────────────────────────────────────
test_start "T1: GET own user via IDCS /admin/v1/Me"
RAW=$(devel_curl GET "${IDCS}/admin/v1/Me")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
ACTIVE=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('active', False))" 2>/dev/null || echo "false")
USERNAME=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('userName','?'))" 2>/dev/null || echo "?")
DOMAIN=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('domainOcid','?'))" 2>/dev/null || echo "?")
if [[ "$ACTIVE" == "True" ]]; then
pass "User retrieved — ${USERNAME} — active=true (HTTP ${CODE})"
detail "domainOcid: ${DOMAIN}"
else
warn "User retrieved but active=${ACTIVE} (HTTP ${CODE})"
fi
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T2: List users in the devel domain
# ──────────────────────────────────────────────
test_start "T2: List users in devel domain"
RAW=$(devel_curl GET "${IDCS}/admin/v1/Users?count=10")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
TOTAL=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('totalResults', 0))" 2>/dev/null || echo "0")
pass "totalResults=${TOTAL} (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T3: List groups in the devel domain
# ──────────────────────────────────────────────
test_start "T3: List groups in devel domain"
RAW=$(devel_curl GET "${IDCS}/admin/v1/Groups?count=10")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
TOTAL=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('totalResults', 0))" 2>/dev/null || echo "0")
pass "totalResults=${TOTAL} (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T4: Verify user has API key capability
# IDCS tracks per-user capability flags in the user profile
# ──────────────────────────────────────────────
test_start "T4: Verify API key capability on devel user"
RAW=$(devel_curl GET "${IDCS}/admin/v1/Me")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
CAN_USE=$(echo "$BODY" | python3 -c "
import sys, json
d = json.load(sys.stdin)
caps = d.get('urn:ietf:params:scim:schemas:oracle:idcs:extension:capabilities:User', {})
print(caps.get('canUseApiKeys', False))
" 2>/dev/null || echo "false")
if [[ "$CAN_USE" == "True" ]]; then
pass "canUseApiKeys=true (HTTP ${CODE})"
else
fail "canUseApiKeys=${CAN_USE} — API key auth would fail"
fi
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T5: GET IDCS domain settings
# ──────────────────────────────────────────────
test_start "T5: GET IDCS domain settings"
RAW=$(devel_curl GET "${IDCS}/admin/v1/Settings")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
TENANT=$(echo "$BODY" | python3 -c "
import sys, json
d = json.load(sys.stdin)
resources = d.get('Resources', [d])
print(resources[0].get('defaultTrustScope', resources[0].get('id', '?')))
" 2>/dev/null || echo "?")
pass "Domain settings accessible (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T6: List apps registered in the devel domain
# ──────────────────────────────────────────────
test_start "T6: List apps in devel domain"
RAW=$(devel_curl GET "${IDCS}/admin/v1/Apps?count=5")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
TOTAL=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('totalResults', 0))" 2>/dev/null || echo "0")
pass "totalResults=${TOTAL} (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T7: GET devel domain metadata via legacy IAM
# Uses DEFAULT credentials — verifies the devel domain is
# visible from the IAM control plane (domain lifecycle, type)
# ──────────────────────────────────────────────
test_start "T7: GET devel domain metadata via legacy IAM (default creds)"
RAW=$(default_curl GET "${DEFAULT_BASE_URL}/20160918/domains/${DEVEL_DOMAIN_OCID}")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "200" ]]; then
DOM_NAME=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('displayName','?'))" 2>/dev/null || echo "?")
DOM_TYPE=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('type','?'))" 2>/dev/null || echo "?")
DOM_STATE=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('lifecycleState','?'))" 2>/dev/null || echo "?")
pass "Domain: ${DOM_NAME} (type=${DOM_TYPE}, state=${DOM_STATE}) (HTTP ${CODE})"
else
fail "HTTP ${CODE} — expected 200"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T8: List compartments via IAM policy
# Devel user has 'manage all-resources in tenancy' IAM policy.
# Legacy IAM /20160918/ rejects IDCS-only users regardless of
# policy — this test validates that security boundary holds.
# ──────────────────────────────────────────────
test_start "T8: IDCS user → legacy IAM compartments (expect 404 — boundary check)"
RAW=$(devel_curl GET "${DEFAULT_BASE_URL}/20160918/compartments?compartmentId=${DEVEL_TENANCY_OCID}&limit=2")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "404" ]]; then
pass "Legacy IAM correctly rejects IDCS-only user — HTTP ${CODE}"
elif [[ "$CODE" == "200" ]]; then
warn "Legacy IAM accepted IDCS user — HTTP ${CODE} (architecture may have changed)"
detail "$BODY"
else
warn "HTTP ${CODE} — unexpected response"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# T9: IDCS OIDC discovery (unauthenticated)
# Smoke-test that the IDCS layer itself is healthy
# ──────────────────────────────────────────────
test_start "T9: IDCS OIDC discovery (unauthenticated)"
OIDC_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
"${IDCS}/.well-known/openid-configuration")
if [[ "$OIDC_CODE" == "200" ]]; then
pass "IDCS OIDC endpoint reachable (HTTP ${OIDC_CODE})"
else
fail "IDCS OIDC endpoint returned HTTP ${OIDC_CODE}"
fi
8 negative tests verifying domain isolation. Covers identity forgery, IDCS domain isolation, legacy IAM isolation, bogus fingerprints on both endpoints, and clock skew enforcement.
| # | Scenario | Expected |
|---|---|---|
| X1 | Default key + devel user OCID → devel IDCS (forgery) | 401 |
| X2 | Devel key + default user OCID → default IAM (forgery) | 401 |
| X3 | Devel creds → default IDCS (no cross-domain IDCS role) | 401 |
| X4 | Devel creds → legacy IAM (IDCS user not in legacy IAM) | 404 |
| X5 | Default creds → GET devel user via legacy IAM | 404 |
| X6 | Bogus fingerprint → default IAM | 401 |
| X7 | Bogus fingerprint → devel IDCS | 401 |
| X8 | Clock skew +10 min → default IAM | 401 |
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Test Suite: CROSS-DOMAIN ISOLATION (negative tests)
#
# Validates the security boundaries between the two domains.
# All tests confirm that invalid or cross-domain credential use
# is properly rejected.
#
# Architecture recap:
# DEFAULT user → authenticates via legacy IAM /20160918/
# DEVEL user → authenticates via IDCS /admin/v1/ SCIM API
# DEVEL user is NOT present in legacy IAM — completely separate.
# ─────────────────────────────────────────────────────────────────
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/env.sh"
source "${SCRIPT_DIR}/lib_oci_sign.sh"
source "${SCRIPT_DIR}/lib_test_helpers.sh"
section "CROSS-DOMAIN ISOLATION (negative tests)"
# ──────────────────────────────────────────────
# X1: Forge identity on devel IDCS
# Sign with DEFAULT key but put DEVEL user OCID in keyId.
# Signature will not validate — IDCS has the default key's
# public key associated to the DEFAULT user, not DEVEL.
# ──────────────────────────────────────────────
test_start "X1: Forged keyId — default key + devel user OCID → devel IDCS (expect 401)"
RAW=$(oci_signed_curl GET \
"${DEVEL_IDCS_URL}/admin/v1/Me" \
"$DEVEL_TENANCY_OCID" "$DEVEL_USER_OCID" \
"$DEFAULT_FINGERPRINT" "$DEFAULT_KEY_PATH")
read_response "$RAW" BODY CODE
if [[ "$CODE" =~ ^(401|403)$ ]]; then
pass "Forged keyId rejected — HTTP ${CODE}"
else
fail "SECURITY CONCERN — forged keyId accepted! HTTP ${CODE}"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# X2: Forge identity on legacy IAM
# Sign with DEVEL key but put DEFAULT user OCID in keyId.
# ──────────────────────────────────────────────
test_start "X2: Forged keyId — devel key + default user OCID → default IAM (expect 401)"
RAW=$(oci_signed_curl GET \
"${DEFAULT_BASE_URL}/20160918/users/${DEFAULT_USER_OCID}" \
"$DEFAULT_TENANCY_OCID" "$DEFAULT_USER_OCID" \
"$DEVEL_FINGERPRINT" "$DEVEL_KEY_PATH")
read_response "$RAW" BODY CODE
if [[ "$CODE" =~ ^(401|403)$ ]]; then
pass "Forged keyId rejected — HTTP ${CODE}"
else
fail "SECURITY CONCERN — forged keyId accepted! HTTP ${CODE}"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# X3: IDCS domain isolation
# Devel user (valid credentials) → default domain IDCS.
# Requires explicit Identity Domain Administrator role in the
# default domain to succeed — should be rejected without it.
# ──────────────────────────────────────────────
test_start "X3: Devel creds → default IDCS /admin/v1/Me (expect 401 — no cross-domain IDCS role)"
RAW=$(devel_curl GET "${DEFAULT_IDCS_URL}/admin/v1/Me")
read_response "$RAW" BODY CODE
if [[ "$CODE" =~ ^(401|403)$ ]]; then
pass "Default IDCS correctly rejected devel user — HTTP ${CODE}"
elif [[ "$CODE" == "200" ]]; then
warn "Devel user has cross-domain IDCS access — HTTP ${CODE} (verify this is intentional)"
detail "$BODY"
else
warn "HTTP ${CODE} — unexpected"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# X4: Legacy IAM isolation
# DEVEL user is IDCS-only — legacy IAM has no record of them.
# Any request to /20160918/ with devel credentials must fail.
# ──────────────────────────────────────────────
test_start "X4: Devel creds → legacy IAM GET user (expect 404 — IDCS user not in legacy IAM)"
RAW=$(devel_curl GET "${DEFAULT_BASE_URL}/20160918/users/${DEVEL_USER_OCID}")
read_response "$RAW" BODY CODE
if [[ "$CODE" =~ ^(401|403|404)$ ]]; then
pass "Legacy IAM correctly rejected IDCS-only user — HTTP ${CODE}"
else
fail "SECURITY CONCERN — legacy IAM accepted IDCS user! HTTP ${CODE}"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# X5: Default creds → attempt to read devel user via legacy IAM
# The devel user OCID does not exist in legacy IAM — must 404.
# ──────────────────────────────────────────────
test_start "X5: Default creds → GET devel user via legacy IAM (expect 404 — IDCS user absent)"
RAW=$(default_curl GET "${DEFAULT_BASE_URL}/20160918/users/${DEVEL_USER_OCID}")
read_response "$RAW" BODY CODE
if [[ "$CODE" =~ ^(401|403|404)$ ]]; then
pass "IDCS user correctly absent from legacy IAM — HTTP ${CODE}"
else
fail "SECURITY CONCERN — IDCS user found in legacy IAM! HTTP ${CODE}"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# X6: Bogus fingerprint — default domain
# ──────────────────────────────────────────────
test_start "X6: Bogus fingerprint → default IAM (expect 401)"
RAW=$(oci_signed_curl GET \
"${DEFAULT_BASE_URL}/20160918/users/${DEFAULT_USER_OCID}" \
"$DEFAULT_TENANCY_OCID" "$DEFAULT_USER_OCID" \
"ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff" \
"$DEFAULT_KEY_PATH")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "401" ]]; then
pass "Bogus fingerprint rejected — HTTP ${CODE}"
else
fail "Bogus fingerprint NOT rejected — HTTP ${CODE}"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# X7: Bogus fingerprint — devel IDCS
# ──────────────────────────────────────────────
test_start "X7: Bogus fingerprint → devel IDCS (expect 401)"
RAW=$(oci_signed_curl GET \
"${DEVEL_IDCS_URL}/admin/v1/Me" \
"$DEVEL_TENANCY_OCID" "$DEVEL_USER_OCID" \
"ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff" \
"$DEVEL_KEY_PATH")
read_response "$RAW" BODY CODE
if [[ "$CODE" == "401" ]]; then
pass "Bogus fingerprint rejected — HTTP ${CODE}"
else
fail "Bogus fingerprint NOT rejected — HTTP ${CODE}"
detail "$BODY"
fi
# ──────────────────────────────────────────────
# X8: Clock skew (+10 min) — default domain
# OCI enforces ±5 min tolerance on request timestamps.
# ──────────────────────────────────────────────
test_start "X8: Clock skew +10 min → default IAM (expect 401)"
SKEWED_DATE=$(date -u -d '+10 minutes' '+%a, %d %b %Y %H:%M:%S GMT' 2>/dev/null \
|| date -u -v+10M '+%a, %d %b %Y %H:%M:%S GMT' 2>/dev/null \
|| echo "")
if [[ -n "$SKEWED_DATE" ]]; then
HOST=$(echo "${DEFAULT_BASE_URL}" | sed 's|https://||' | cut -d'/' -f1)
REQUEST_TARGET="/20160918/users/${DEFAULT_USER_OCID}"
KEY_ID="${DEFAULT_TENANCY_OCID}/${DEFAULT_USER_OCID}/${DEFAULT_FINGERPRINT}"
SIGNING_STRING="(request-target): get ${REQUEST_TARGET}
date: ${SKEWED_DATE}
host: ${HOST}"
SIGNATURE=$(printf '%s' "$SIGNING_STRING" \
| openssl dgst -sha256 -sign "$DEFAULT_KEY_PATH" \
| base64 | tr -d '\n')
AUTH_HEADER="Signature version=\"1\",keyId=\"${KEY_ID}\",algorithm=\"rsa-sha256\",headers=\"(request-target) date host\",signature=\"${SIGNATURE}\""
SKEW_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X GET \
"${DEFAULT_BASE_URL}${REQUEST_TARGET}" \
-H "date: ${SKEWED_DATE}" \
-H "host: ${HOST}" \
-H "Authorization: ${AUTH_HEADER}")
if [[ "$SKEW_CODE" == "401" ]]; then
pass "Clock-skewed request rejected — HTTP ${SKEW_CODE}"
else
warn "Clock-skewed request returned HTTP ${SKEW_CODE} (OCI allows ±5 min)"
fi
else
skip "Could not generate skewed date on this OS"
fi
Verifies DEFAULT domain authentication by calling GET /20160918/users/{ocid} with the legacy IAM API key.
Prints the authenticated user's name, lifecycle state and tenancy. Can be sourced to export env vars into the current shell.
./login_default.sh — verify and print result
source ./login_default.sh — verify and export env vars into current shell
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Login / auth-verify for the DEFAULT domain (Legacy IAM)
# Credentials sourced from ~/.oci/config [DEFAULT] profile.
#
# Usage:
# ./login_default.sh — verify and print result
# source ./login_default.sh — also export env vars into shell
# ─────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib_oci_sign.sh"
# ── Credentials from ~/.oci/config [DEFAULT] ──
export OCI_REGION="eu-frankfurt-1"
export DEFAULT_TENANCY_OCID="ocid1.tenancy.oc1..aaaaaaaafwvqjti6nzubd3yf6obb2amwmr5c43j72n65gbbfhumrq2wf4f2a"
export DEFAULT_USER_OCID="ocid1.user.oc1..aaaaaaaaxxagu32ikzlgsylotiogywsr7wwa3redkrcfihwcxhke7uuqytuq"
export DEFAULT_FINGERPRINT="c8:ac:83:36:50:13:45:17:e9:bb:2a:41:66:93:57:fb"
export DEFAULT_KEY_PATH="${HOME}/.oci/oci_api_key.pem"
export DEFAULT_BASE_URL="https://identity.${OCI_REGION}.oraclecloud.com"
# ── Verify key file exists ──
if [[ ! -f "$DEFAULT_KEY_PATH" ]]; then
echo -e "\033[0;31m[FAIL]\033[0m Key file not found: ${DEFAULT_KEY_PATH}"
return 1 2>/dev/null || exit 1
fi
echo "Authenticating to DEFAULT domain (Legacy IAM)..."
RAW=$(oci_signed_curl GET \
"${DEFAULT_BASE_URL}/20160918/users/${DEFAULT_USER_OCID}" \
"$DEFAULT_TENANCY_OCID" "$DEFAULT_USER_OCID" \
"$DEFAULT_FINGERPRINT" "$DEFAULT_KEY_PATH")
HTTP_CODE=$(echo "$RAW" | tail -1)
BODY=$(echo "$RAW" | sed '$d')
if [[ "$HTTP_CODE" == "200" ]]; then
DISPLAY_NAME=$(echo "$BODY" | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d.get('name', d.get('description','?')))" 2>/dev/null || echo "?")
LIFECYCLE=$(echo "$BODY" | python3 -c \
"import sys,json; print(json.load(sys.stdin).get('lifecycleState','?'))" 2>/dev/null || echo "?")
echo -e "\033[0;32m[OK]\033[0m DEFAULT domain login verified"
echo " User: ${DISPLAY_NAME}"
echo " State: ${LIFECYCLE}"
echo " Tenant: ${DEFAULT_TENANCY_OCID}"
else
echo -e "\033[0;31m[FAIL]\033[0m HTTP ${HTTP_CODE} — authentication failed"
echo "$BODY"
return 1 2>/dev/null || exit 1
fi
Verifies DEVEL domain authentication via the IDCS /admin/v1/Me endpoint.
The devel user is IDCS-only — not accessible via legacy IAM.
Exports DEVEL_IDCS_URL and DEVEL_DOMAIN_OCID when sourced.
./login_devel.sh — verify and print result
source ./login_devel.sh — verify and export env vars into current shell
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Login / auth-verify for the DEVEL identity domain
# Credentials sourced from ~/.oci/config [ryzior_devel] profile.
#
# The devel domain uses the new IDCS model — users live under the
# IDCS SCIM endpoint, not the legacy IAM /20160918/users/ path.
# Authentication still uses OCI API Key Signature v1 (RSA-SHA256).
#
# Usage:
# ./login_devel.sh — verify and print result
# source ./login_devel.sh — also export env vars into shell
# ─────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib_oci_sign.sh"
# ── Credentials from ~/.oci/config [ryzior_devel] ──
export OCI_REGION="eu-frankfurt-1"
export DEVEL_TENANCY_OCID="ocid1.tenancy.oc1..aaaaaaaafwvqjti6nzubd3yf6obb2amwmr5c43j72n65gbbfhumrq2wf4f2a"
export DEVEL_USER_OCID="ocid1.user.oc1..aaaaaaaanwjvwebk3xk4b5fnhyxk5skhy6oesspvtftll6uxftmcbmbrxuuq"
export DEVEL_FINGERPRINT="0a:e3:74:04:8a:66:bd:b7:bc:d2:77:64:ee:eb:80:80"
export DEVEL_KEY_PATH="${HOME}/.oci/ryzior_oci_api_key.pem"
export DEVEL_BASE_URL="https://identity.${OCI_REGION}.oraclecloud.com"
export DEVEL_DOMAIN_OCID="ocid1.domain.oc1..aaaaaaaajmqq63n33q5msygn6pqlxopaoxvplec6vscf4xhkr67wjkyxqtda"
export DEVEL_IDCS_URL="https://idcs-a8490e61856a4816858a942ef5289a6c.identity.oraclecloud.com"
# ── Verify key file exists ──
if [[ ! -f "$DEVEL_KEY_PATH" ]]; then
echo -e "\033[0;31m[FAIL]\033[0m Key file not found: ${DEVEL_KEY_PATH}"
return 1 2>/dev/null || exit 1
fi
echo "Authenticating to DEVEL identity domain (IDCS)..."
RAW=$(oci_signed_curl GET \
"${DEVEL_IDCS_URL}/admin/v1/Me" \
"$DEVEL_TENANCY_OCID" "$DEVEL_USER_OCID" \
"$DEVEL_FINGERPRINT" "$DEVEL_KEY_PATH")
HTTP_CODE=$(echo "$RAW" | tail -1)
BODY=$(echo "$RAW" | sed '$d')
if [[ "$HTTP_CODE" == "200" ]]; then
DISPLAY_NAME=$(echo "$BODY" | python3 -c \
"import sys,json; print(json.load(sys.stdin).get('displayName','?'))" 2>/dev/null || echo "?")
USER_NAME=$(echo "$BODY" | python3 -c \
"import sys,json; print(json.load(sys.stdin).get('userName','?'))" 2>/dev/null || echo "?")
ACTIVE=$(echo "$BODY" | python3 -c \
"import sys,json; print('ACTIVE' if json.load(sys.stdin).get('active') else 'INACTIVE')" 2>/dev/null || echo "?")
echo -e "\033[0;32m[OK]\033[0m DEVEL domain login verified"
echo " User: ${DISPLAY_NAME} (${USER_NAME})"
echo " State: ${ACTIVE}"
echo " Domain: ${DEVEL_DOMAIN_OCID}"
else
echo -e "\033[0;31m[FAIL]\033[0m HTTP ${HTTP_CODE} — authentication failed"
echo "$BODY"
return 1 2>/dev/null || exit 1
fi
Authenticates as the DEFAULT domain user (legacy IAM) and creates a new user in the DEVEL identity domain via
the IDCS /admin/v1/Users SCIM endpoint. The default user's API key is trusted by the devel IDCS
because they hold tenancy-level admin. Email is derived as <username>@ryzior.com.
Displays the acting identity before creating the user.
./create_crosstest_user.sh <username>
Example: ./create_crosstest_user.sh alice → creates alice / alice@ryzior.com in DEVEL domain
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Create user 'crosstest' in the DEVEL identity domain
# authenticated as the DEFAULT domain user (admin).
#
# Signing: OCI API Key Signature v1 (RSA-SHA256), default creds.
# Target: DEVEL domain IDCS SCIM endpoint /admin/v1/Users
# ─────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib_oci_sign.sh"
# ── DEFAULT domain credentials (from ~/.oci/config [DEFAULT]) ──
TENANCY_OCID="ocid1.tenancy.oc1..aaaaaaaafwvqjti6nzubd3yf6obb2amwmr5c43j72n65gbbfhumrq2wf4f2a"
DEFAULT_USER_OCID="ocid1.user.oc1..aaaaaaaaxxagu32ikzlgsylotiogywsr7wwa3redkrcfihwcxhke7uuqytuq"
DEFAULT_FINGERPRINT="c8:ac:83:36:50:13:45:17:e9:bb:2a:41:66:93:57:fb"
DEFAULT_KEY_PATH="${HOME}/.oci/oci_api_key.pem"
DEFAULT_IAM_URL="https://identity.eu-frankfurt-1.oraclecloud.com"
DEVEL_IDCS_URL="https://idcs-a8490e61856a4816858a942ef5289a6c.identity.oraclecloud.com"
USERNAME="${1:-}"
if [[ -z "$USERNAME" ]]; then
echo "Usage: $0 <username>"
exit 1
fi
EMAIL="${USERNAME}@ryzior.com"
if [[ ! -f "$DEFAULT_KEY_PATH" ]]; then
echo -e "\033[0;31m[FAIL]\033[0m Key file not found: ${DEFAULT_KEY_PATH}"
exit 1
fi
# ── Step 1: resolve and display the acting identity ──
RAW=$(oci_signed_curl GET "${DEFAULT_IAM_URL}/20160918/users/${DEFAULT_USER_OCID}" \
"$TENANCY_OCID" "$DEFAULT_USER_OCID" "$DEFAULT_FINGERPRINT" "$DEFAULT_KEY_PATH")
HTTP_CODE=$(echo "$RAW" | tail -1)
ME=$(echo "$RAW" | sed '$d')
if [[ "$HTTP_CODE" != "200" ]]; then
echo -e "\033[0;31m[FAIL]\033[0m Could not resolve acting identity (HTTP ${HTTP_CODE})"
echo "$ME"
exit 1
fi
ACTING_NAME=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('name','?'))" 2>/dev/null)
ACTING_EMAIL=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('email','?'))" 2>/dev/null)
ACTING_OCID=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id','?'))" 2>/dev/null)
ACTING_STATE=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('lifecycleState','?'))" 2>/dev/null)
echo "Acting identity (DEFAULT domain):"
echo " name: ${ACTING_NAME} (${ACTING_EMAIL})"
echo " ocid: ${ACTING_OCID}"
echo " state: ${ACTING_STATE}"
echo " domain: Default (legacy IAM)"
echo ""
# ── Step 2: create user in DEVEL domain ──
echo "Creating user '${USERNAME}' in DEVEL domain..."
BODY=$(python3 -c "import json,sys; u,e=sys.argv[1],sys.argv[2]; print(json.dumps({
'schemas': ['urn:ietf:params:scim:schemas:core:2.0:User'],
'userName': u,
'name': {'givenName': u, 'familyName': u},
'emails': [{'value': e, 'type': 'work', 'primary': True}]
}))" "$USERNAME" "$EMAIL")
RAW=$(oci_signed_curl POST "${DEVEL_IDCS_URL}/admin/v1/Users" \
"$TENANCY_OCID" "$DEFAULT_USER_OCID" \
"$DEFAULT_FINGERPRINT" "$DEFAULT_KEY_PATH" \
"$BODY")
HTTP_CODE=$(echo "$RAW" | tail -1)
BODY_RESP=$(echo "$RAW" | sed '$d')
if [[ "$HTTP_CODE" == "201" ]]; then
NEW_ID=$(echo "$BODY_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id','?'))" 2>/dev/null)
NEW_OCID=$(echo "$BODY_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('ocid','?'))" 2>/dev/null)
NEW_DOMAIN=$(echo "$BODY_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('domainOcid','?'))" 2>/dev/null)
echo -e "\033[0;32m[OK]\033[0m User created"
echo " userName: ${USERNAME}"
echo " email: ${EMAIL}"
echo " id: ${NEW_ID}"
echo " ocid: ${NEW_OCID}"
echo " domain: ${NEW_DOMAIN}"
echo ""
echo " Created by: ${ACTING_NAME} (${ACTING_OCID})"
elif [[ "$HTTP_CODE" == "409" ]]; then
echo -e "\033[0;33m[WARN]\033[0m User '${USERNAME}' already exists in DEVEL domain (HTTP 409)"
else
echo -e "\033[0;31m[FAIL]\033[0m HTTP ${HTTP_CODE}"
echo "$BODY_RESP"
exit 1
fi
Authenticates as the DEVEL domain user (IDCS) and creates a new user in the DEFAULT identity domain via
the default domain's IDCS /admin/v1/Users endpoint. Requires IAM policy:
ALLOW GROUP 'devel'/'Domain Administrators' to manage all-resources IN TENANCY.
Resolves and displays the acting identity (via IDCS /admin/v1/Me) before creating the user.
./create_crosstest_in_default.sh <username>
Example: ./create_crosstest_in_default.sh bob → creates bob / bob@ryzior.com in DEFAULT domain
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────
# Create user 'crosstest' in the DEFAULT domain
# authenticated as the DEVEL domain user (tenancy admin via policy).
#
# Signing: OCI API Key Signature v1 (RSA-SHA256), devel creds.
# Target: DEFAULT domain IDCS SCIM endpoint /admin/v1/Users
# ─────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib_oci_sign.sh"
# ── DEVEL domain credentials (from ~/.oci/config [ryzior_devel]) ──
TENANCY_OCID="ocid1.tenancy.oc1..aaaaaaaafwvqjti6nzubd3yf6obb2amwmr5c43j72n65gbbfhumrq2wf4f2a"
DEVEL_USER_OCID="ocid1.user.oc1..aaaaaaaanwjvwebk3xk4b5fnhyxk5skhy6oesspvtftll6uxftmcbmbrxuuq"
DEVEL_FINGERPRINT="0a:e3:74:04:8a:66:bd:b7:bc:d2:77:64:ee:eb:80:80"
DEVEL_KEY_PATH="${HOME}/.oci/ryzior_oci_api_key.pem"
DEVEL_IDCS_URL="https://idcs-a8490e61856a4816858a942ef5289a6c.identity.oraclecloud.com"
DEFAULT_IDCS_URL="https://idcs-4a915662984e434495247863514dc115.identity.oraclecloud.com"
USERNAME="${1:-}"
if [[ -z "$USERNAME" ]]; then
echo "Usage: $0 <username>"
exit 1
fi
EMAIL="${USERNAME}@ryzior.com"
if [[ ! -f "$DEVEL_KEY_PATH" ]]; then
echo -e "\033[0;31m[FAIL]\033[0m Key file not found: ${DEVEL_KEY_PATH}"
exit 1
fi
# ── Step 1: resolve and display the acting identity ──
RAW=$(oci_signed_curl GET "${DEVEL_IDCS_URL}/admin/v1/Me" \
"$TENANCY_OCID" "$DEVEL_USER_OCID" "$DEVEL_FINGERPRINT" "$DEVEL_KEY_PATH")
HTTP_CODE=$(echo "$RAW" | tail -1)
ME=$(echo "$RAW" | sed '$d')
if [[ "$HTTP_CODE" != "200" ]]; then
echo -e "\033[0;31m[FAIL]\033[0m Could not resolve acting identity (HTTP ${HTTP_CODE})"
echo "$ME"
exit 1
fi
ACTING_NAME=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('displayName','?'))" 2>/dev/null)
ACTING_USER=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('userName','?'))" 2>/dev/null)
ACTING_OCID=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('ocid','?'))" 2>/dev/null)
ACTING_DOMAIN=$(echo "$ME" | python3 -c "import sys,json; print(json.load(sys.stdin).get('domainOcid','?'))" 2>/dev/null)
echo "Acting identity:"
echo " name: ${ACTING_NAME} (${ACTING_USER})"
echo " ocid: ${ACTING_OCID}"
echo " domain: ${ACTING_DOMAIN}"
echo ""
# ── Step 2: create user in DEFAULT domain ──
echo "Creating user '${USERNAME}' in DEFAULT domain..."
BODY=$(python3 -c "import json,sys; u,e=sys.argv[1],sys.argv[2]; print(json.dumps({
'schemas': ['urn:ietf:params:scim:schemas:core:2.0:User'],
'userName': u,
'name': {'givenName': u, 'familyName': u},
'emails': [{'value': e, 'type': 'work', 'primary': True}]
}))" "$USERNAME" "$EMAIL")
RAW=$(oci_signed_curl POST "${DEFAULT_IDCS_URL}/admin/v1/Users" \
"$TENANCY_OCID" "$DEVEL_USER_OCID" "$DEVEL_FINGERPRINT" "$DEVEL_KEY_PATH" \
"$BODY")
HTTP_CODE=$(echo "$RAW" | tail -1)
BODY_RESP=$(echo "$RAW" | sed '$d')
if [[ "$HTTP_CODE" == "201" ]]; then
NEW_ID=$(echo "$BODY_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id','?'))" 2>/dev/null)
NEW_OCID=$(echo "$BODY_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('ocid','?'))" 2>/dev/null)
NEW_DOMAIN=$(echo "$BODY_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('domainOcid','?'))" 2>/dev/null)
echo -e "\033[0;32m[OK]\033[0m User created"
echo " userName: ${USERNAME}"
echo " email: ${EMAIL}"
echo " id: ${NEW_ID}"
echo " ocid: ${NEW_OCID}"
echo " domain: ${NEW_DOMAIN}"
echo ""
echo " Created by: ${ACTING_NAME} (${ACTING_OCID})"
elif [[ "$HTTP_CODE" == "409" ]]; then
echo -e "\033[0;33m[WARN]\033[0m User '${USERNAME}' already exists in DEFAULT domain (HTTP 409)"
else
echo -e "\033[0;31m[FAIL]\033[0m HTTP ${HTTP_CODE}"
echo "$BODY_RESP"
exit 1
fi
Common failure patterns and what they mean.
| Symptom | Cause |
|---|---|
| 401 NotAuthenticated | Wrong fingerprint, OCID, or key file mismatch |
| 401 SignatureError | Clock skew > 5 min, or signing string assembled incorrectly |
| 404 on devel domain tests | Using /20160918/ instead of IDCS /admin/v1/ endpoint — devel user is IDCS-only |
| 401 accessDenied on default IDCS | Devel user has no Identity Domain Administrator role in the default domain |
| X3 returns 200 | Review — devel user has been granted IDCS admin in default domain |
| T8 devel suite shows WARN | IAM policy grants legacy IAM read access to IDCS user — verify if intentional |
| Cross-domain returns 200 unexpectedly | Security issue — investigate policy leakage |
| All cross-domain tests pass (4xx) | Good — domains are properly isolated |
./run_tests.sh && echo "OCI auth OK" || { echo "ALERT: auth smoke test FAILED"; exit 1; }