OCI IAM Unused User Credentials Should Be Disabled
More Info:
Credentials for users inactive for 90+ days should be disabled. Dormant accounts with active credentials are prime targets for attackers since suspicious activity may go unnoticed.
Risk Level
Medium
Address
Compliance, Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS AWS
- CIS Critical Security Controls v8
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- Cloudanix Best Practice
- DPDPA
- Digital Operational Resilience Act (EU)
- Essential 8
- GDPR
- HIPAA
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIS2 Directive
- NIST
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- SOC2
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Using Console
Below are the exact console steps to remediate the “OCI IAM Unused User Credentials Should Be Disabled” finding, assuming it comes from IAM Monitoring / Security Advisor / Cloud Guard.
1. Identify the affected user and credential type
- Sign in to the OCI Console.
- Open the Navigation Menu → Identity & Security → Identity → Users.
- Locate the user flagged by IAM Monitoring:
- Use the Search box if needed.
- Click the user name to open the User Details page.
You now need to disable each type of unused credential for that user.
2. Disable unused Console password (local user)
Applies only to local OCI users (not IdP‑federated).
- On the user’s page, in the Resources panel, click Auth Tokens / Customer Secret Keys / API Keys / Console Access (names may vary slightly by tenancy / console version).
- For console password, either:
- Disable the user (hard stop for console login), or
- Remove/clear the local password (if shown as a dedicated option).
- To fully block console access for that user, under User Details:
- Set User Status to Inactive (or use Disable action from the user list).
This ensures the user cannot log into the OCI console.
3. Revoke unused API keys
- Still on the user’s details page, in Resources, click API Keys.
- Review the keys; for each unused key (as indicated by IAM Monitoring finding / last used metadata):
- Click the Actions (⋮) menu next to the key.
- Click Delete and confirm.
This prevents CLI/SDK access using that key.
4. Revoke unused Auth Tokens
- In Resources, click Auth Tokens.
- For each token identified as unused:
- Click Revoke (or Delete) from the Actions (⋮) menu.
- Confirm the revocation.
This blocks access using auth tokens (e.g., for certain tools or services).
5. Revoke unused SMTP credentials (if any)
- In Resources, click SMTP Credentials (or Email Delivery Credentials).
- For each credential set flagged as unused:
- Click Revoke or Delete in the Actions (⋮) menu.
- Confirm.
This stops email sending with those credentials.
6. Revoke unused Customer Secret Keys (Object Storage/Signing keys)
- In Resources, click Customer Secret Keys.
- For each key found unused:
- Click Revoke or Delete from the Actions (⋮) menu.
- Confirm.
This blocks signed object storage / S3‑compatible access from those keys.
7. (Optional) Fully disable or delete dormant users
If all credentials are unused and the user is no longer needed:
- Go back to Identity & Security → Identity → Users.
- Find the user.
- From the Actions (⋮) menu on the right:
- Choose Disable to deactivate the user, or
- Delete if you’re sure the identity is no longer required.
8. Re-check the IAM Monitoring finding
- Go to Identity & Security → Security Center / Cloud Guard / Security Advisor (depending on your setup).
- Locate the original “Unused User Credentials Should Be Disabled” problem.
- Mark it as Resolved or wait for the next evaluation cycle to confirm the status is Closed, depending on the service behavior.
If you share which credential type (API key, auth token, console password, etc.) the IAM Monitoring alert is about, I can give a one‑page, credential‑specific version of the steps.
Using CLI
Below is a concise, CLI-focused way to identify and disable unused IAM user credentials in OCI, suitable for wiring into monitoring/remediation.
Assumptions:
- You already know your compartment OCID (for listing users).
- “Unused” means “user not seen in Audit logs in X days” (e.g., 90).
- You want to disable the user account and/or delete access keys/tokens.
1. Set variables
# Adjust these
TENANCY_OCID="<your-tenancy-ocid>"
COMPARTMENT_OCID="<your-iam-compartment-ocid>" # often same as tenancy
DAYS_UNUSED=90
REGION="us-ashburn-1" # adjust
2. Get all IAM users in the compartment
oci iam user list \
--compartment-id "$COMPARTMENT_OCID" \
--all \
--query "data[].{id:id, name:name}" \
--output json > users.json
3. Function to check if a user has activity in the last N days
This uses the Audit service to see if any events were generated by this user.
cutoff_date=$(date -u -d "-$DAYS_UNUSED days" +"%Y-%m-%dT%H:%M:%SZ")
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
has_recent_activity() {
local user_ocid="$1"
local count
count=$(oci audit event list \
--compartment-id "$TENANCY_OCID" \
--start-time "$cutoff_date" \
--end-time "$now" \
--query "data[?identity.principalId=='$user_ocid'] | length(@)" \
--region "$REGION" \
--all \
--output json)
# If count > 0, user has recent activity
[ "$count" -gt 0 ]
}
4. Disable unused users and remove their programmatic credentials
Warning: This will disable users and remove keys/tokens. Test first with echo-only.
jq -c '.[]' users.json | while read -r user; do
user_id=$(echo "$user" | jq -r '.id')
user_name=$(echo "$user" | jq -r '.name')
# Skip already INACTIVE users
state=$(oci iam user get --user-id "$user_id" --query "data.\"lifecycle-state\"" --output text)
if [ "$state" = "INACTIVE" ]; then
echo "User $user_name ($user_id) already INACTIVE, skipping"
continue
fi
if has_recent_activity "$user_id"; then
echo "User $user_name ($user_id) has activity in last $DAYS_UNUSED days, skipping"
continue
fi
echo "User $user_name ($user_id) has NO activity in last $DAYS_UNUSED days – remediating"
# 4.1 Delete API keys
oci iam api-key list \
--user-id "$user_id" \
--query "data[].fingerprint" \
--output json | jq -r '.[]' | while read -r fp; do
echo " Deleting API key $fp for $user_name"
oci iam api-key delete --user-id "$user_id" --fingerprint "$fp" --force
done
# 4.2 Delete Auth Tokens
oci iam auth-token list \
--user-id "$user_id" \
--query "data[].id" \
--output json | jq -r '.[]' | while read -r token_id; do
echo " Deleting Auth Token $token_id for $user_name"
oci iam auth-token delete --user-id "$user_id" --auth-token-id "$token_id" --force
done
# 4.3 (Optional) Delete SMTP credentials
oci iam smtp-credential list \
--user-id "$user_id" \
--query "data[].id" \
--output json | jq -r '.[]' | while read -r smtp_id; do
echo " Deleting SMTP credential $smtp_id for $user_name"
oci iam smtp-credential delete --user-id "$user_id" --smtp-credential-id "$smtp_id" --force
done
# 4.4 Disable the user (console login disabled)
echo " Disabling user $user_name"
oci iam user update \
--user-id "$user_id" \
--state INACTIVE \
--force
done
5. (Optional) Only disable credentials, not the user
If your policy says “disable credentials but keep user active”:
- Keep user
--stateasACTIVE. - Only delete API keys / Auth Tokens / SMTP credentials (steps 4.1–4.3).
- Optionally, force a console password reset:
oci iam user change-password \
--user-id "$user_id" \
--new-password "<TemporaryStrongPassword123!>" \
--force-change-next-login true
6. Integrate with Monitoring / Scheduled Remediation
- Put this logic in a script (e.g.,
disable-unused-iam-users.sh). - Run from:
- An OCI Compute instance with a dynamic group + policy to manage IAM, or
- Cloud Shell on a schedule via an external scheduler (e.g., Jenkins, GitHub Actions).
- Ensure IAM policy (in root compartment or relevant one) permits:
Allow dynamic-group <dg-name> to manage users in tenancy
Allow dynamic-group <dg-name> to read audit-events in tenancy
This gives you a full CLI-based remediation loop for “OCI IAM Unused User Credentials Should Be Disabled.”
Using Python
Below is one practical way to do this with Python + OCI SDK:
- Detect IAM users with no activity for N days (via Audit service)
- For those users:
- Deactivate their API keys
- Delete their auth tokens
- Optionally disable the user account (
lifecycle_state = INACTIVE)
You can then run this as a scheduled job (OCI Functions, OCI DevOps, cron on a compute instance, etc.).
1. Prerequisites
-
Install the OCI Python SDK:
pip install oci -
Configure your OCI CLI/SDK config (e.g.
~/.oci/config):[DEFAULT]user=ocid1.user.oc1..aaaa...fingerprint=...key_file=/path/to/oci_api_key.pemtenancy=ocid1.tenancy.oc1..aaaa...region=us-ashburn-1 -
The principal (user or instance principal) running the script needs IAM policies like:
Allow group SecurityAutomation to inspect users in tenancyAllow group SecurityAutomation to manage api-keys in tenancyAllow group SecurityAutomation to manage auth-tokens in tenancyAllow group SecurityAutomation to inspect compartments in tenancyAllow group SecurityAutomation to read audit-events in tenancyAllow group SecurityAutomation to use users in tenancyIf you want to set users to INACTIVE:
Allow group SecurityAutomation to manage users in tenancy
2. Logic Overview
- List all IAM users.
- For each user:
- Query Audit events for that user in the last
Ndays. - If no events in that period, consider credentials “unused.”
- Query Audit events for that user in the last
- For such users:
- List API keys and deactivate/delete them.
- List auth tokens and delete them.
- (Optional) set user
lifecycle_statetoINACTIVE.
You can tune:
INACTIVITY_DAYS(e.g., 90).- Whether you disable specific credentials or entire user.
3. Example Python Script
import oci
from datetime import datetime, timedelta, timezone
# ==== CONFIG ====
PROFILE = "DEFAULT" # profile in ~/.oci/config
INACTIVITY_DAYS = 90 # threshold for "unused"
DRY_RUN = True # True: just print actions, False: perform them
DISABLE_USER = False # True: set unused users to INACTIVE
# =================
def get_clients(profile):
config = oci.config.from_file(profile_name=profile)
identity_client = oci.identity.IdentityClient(config)
audit_client = oci.audit.AuditClient(config)
return config, identity_client, audit_client
def list_all_users(identity_client, tenancy_ocid):
users = []
response = oci.pagination.list_call_get_all_results(
identity_client.list_users,
compartment_id=tenancy_ocid
)
users.extend(response.data)
return users
def user_has_recent_activity(audit_client, tenancy_ocid, user_ocid, since_dt):
"""
Check Audit events for this user since 'since_dt'.
If any events exist, return True.
"""
start_time = since_dt
end_time = datetime.now(timezone.utc)
# Audit filter by principalId
# Note: Some actions for a user may be performed under different principals (groups, dynamic groups),
# but for “user credentials unused” this is usually adequate.
try:
events = oci.pagination.list_call_get_all_results(
audit_client.list_events,
compartment_id=tenancy_ocid,
start_time=start_time,
end_time=end_time,
principal_id=user_ocid
).data
except oci.exceptions.ServiceError as e:
print(f"Error retrieving audit events for {user_ocid}: {e}")
return True # fail-safe: treat as active
return len(events) > 0
def deactivate_api_keys(identity_client, user):
# Get API keys
api_keys = oci.pagination.list_call_get_all_results(
identity_client.list_api_keys,
user_id=user.id
).data
for key in api_keys:
print(f" Found API key {key.key_id} for user {user.name} ({user.id})")
if DRY_RUN:
print(" DRY RUN: would delete API key")
else:
identity_client.delete_api_key(user_id=user.id, fingerprint=key.fingerprint)
print(" Deleted API key")
def delete_auth_tokens(identity_client, user):
tokens = oci.pagination.list_call_get_all_results(
identity_client.list_auth_tokens,
user_id=user.id
).data
for token in tokens:
print(f" Found auth token {token.description} ({token.id}) for user {user.name} ({user.id})")
if DRY_RUN:
print(" DRY RUN: would delete auth token")
else:
identity_client.delete_auth_token(user_id=user.id, auth_token_id=token.id)
print(" Deleted auth token")
def disable_user(identity_client, user):
if user.lifecycle_state == "INACTIVE":
print(f" User {user.name} already INACTIVE")
return
if DRY_RUN:
print(f" DRY RUN: would set user {user.name} ({user.id}) to INACTIVE")
else:
update_details = oci.identity.models.UpdateUserDetails(
lifecycle_state="INACTIVE"
)
identity_client.update_user(user_id=user.id, update_user_details=update_details)
print(f" Set user {user.name} to INACTIVE")
def main():
config, identity_client, audit_client = get_clients(PROFILE)
tenancy_ocid = config["tenancy"]
since_dt = datetime.now(timezone.utc) - timedelta(days=INACTIVITY_DAYS)
print(f"Checking for users with no activity since {since_dt.isoformat()}")
users = list_all_users(identity_client, tenancy_ocid)
print(f"Found {len(users)} users")
for user in users:
# Skip already inactive users if focusing only on active credentials
if user.lifecycle_state != "ACTIVE":
continue
print(f"\nEvaluating user: {user.name} ({user.id})")
active = user_has_recent_activity(audit_client, tenancy_ocid, user.id, since_dt)
if active:
print(" User has recent activity, credentials considered in use.")
continue
print(" No recent activity found; treating credentials as UNUSED.")
# Disable credentials
deactivate_api_keys(identity_client, user)
delete_auth_tokens(identity_client, user)
if DISABLE_USER:
disable_user(identity_client, user)
if __name__ == "__main__":
main()
4. How to Use / Adapt
- Edit the config section at top:
PROFILE,INACTIVITY_DAYS,DRY_RUN,DISABLE_USER.
- Run once in
DRY_RUN = Trueto verify behavior. - When satisfied:
- Set
DRY_RUN = False. - Optionally
DISABLE_USER = Trueif you want to fully disable users.
- Set
- Schedule the script (e.g., cron, OCI Functions, or OCI DevOps job) to enforce “unused credentials disabled” continuously.
If you tell me:
- whether you’re using Identity Domains, and
- whether you want to only disable API keys/tokens or also console passwords,
I can adjust the script specifically for that setup.
Using Terraform
Terraform cannot disable or rotate OCI IAM user credentials based on “unused for 90+ days” because that condition depends on runtime activity (last-used timestamps), which the provider does not expose as arguments.
You can:
- Use Cloud Guard or custom scripts (SDK/CLI) to detect users/API keys not used in 90+ days, and
- Manually or programmatically disable those credentials via Console/CLI/SDK (e.g., deactivate API keys, disable the user).
This behavior (conditional on inactivity duration) cannot be expressed or enforced directly in oci_identity_user or related Terraform resources, so terraform plan will show no relevant configurable argument for “inactive for 90 days” or similar.