> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        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

        1. Sign in to the **OCI Console**.
        2. Open the **Navigation Menu** → **Identity & Security** → **Identity** → **Users**.
        3. Locate the user flagged by IAM Monitoring:
           * Use the **Search** box if needed.
        4. 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).

        1. 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).
        2. For console password, either:
           * **Disable the user** (hard stop for console login), or
           * Remove/clear the local password (if shown as a dedicated option).
        3. 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

        1. Still on the user’s details page, in **Resources**, click **API Keys**.
        2. 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

        1. In **Resources**, click **Auth Tokens**.
        2. 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)

        1. In **Resources**, click **SMTP Credentials** (or **Email Delivery Credentials**).
        2. 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)

        1. In **Resources**, click **Customer Secret Keys**.
        2. 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:

        1. Go back to **Identity & Security** → **Identity** → **Users**.
        2. Find the user.
        3. 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

        1. Go to **Identity & Security** → **Security Center / Cloud Guard / Security Advisor** (depending on your setup).
        2. Locate the original “Unused User Credentials Should Be Disabled” problem.
        3. 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.
      </Accordion>

      <Accordion title="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

        ```bash theme={null}
        # 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

        ```bash theme={null}
        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.

        ```bash theme={null}
        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.

        ```bash theme={null}
        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 `--state` as `ACTIVE`.
        * Only delete API keys / Auth Tokens / SMTP credentials (steps 4.1–4.3).
        * Optionally, force a console password reset:

        ```bash theme={null}
        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:

        ```text theme={null}
        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.”
      </Accordion>

      <Accordion title="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

        1. Install the OCI Python SDK:
           ```bash theme={null}
           pip install oci
           ```

        2. Configure your OCI CLI/SDK config (e.g. `~/.oci/config`):
           ```ini theme={null}
           [DEFAULT]
           user=ocid1.user.oc1..aaaa...
           fingerprint=...
           key_file=/path/to/oci_api_key.pem
           tenancy=ocid1.tenancy.oc1..aaaa...
           region=us-ashburn-1
           ```

        3. The principal (user or instance principal) running the script needs IAM policies like:

           ```text theme={null}
           Allow group SecurityAutomation to inspect users in tenancy
           Allow group SecurityAutomation to manage api-keys in tenancy
           Allow group SecurityAutomation to manage auth-tokens in tenancy
           Allow group SecurityAutomation to inspect compartments in tenancy
           Allow group SecurityAutomation to read audit-events in tenancy
           Allow group SecurityAutomation to use users in tenancy
           ```

           If you want to set users to INACTIVE:

           ```text theme={null}
           Allow group SecurityAutomation to manage users in tenancy
           ```

        ***

        ## 2. Logic Overview

        1. List all IAM users.
        2. For each user:
           * Query Audit events for that user in the last `N` days.
           * If **no events** in that period, consider credentials “unused.”
        3. For such users:
           * List API keys and deactivate/delete them.
           * List auth tokens and delete them.
           * (Optional) set user `lifecycle_state` to `INACTIVE`.

        You can tune:

        * `INACTIVITY_DAYS` (e.g., 90).
        * Whether you disable specific credentials or entire user.

        ***

        ## 3. Example Python Script

        ```python theme={null}
        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

        1. Edit the config section at top:
           * `PROFILE`, `INACTIVITY_DAYS`, `DRY_RUN`, `DISABLE_USER`.
        2. Run once in `DRY_RUN = True` to verify behavior.
        3. When satisfied:
           * Set `DRY_RUN = False`.
           * Optionally `DISABLE_USER = True` if you want to fully disable users.
        4. 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.
      </Accordion>

      <Accordion title="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.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
