Skip to main content

OCI IAM Policies Should Grant Permissions To Groups Not

More Info:

IAM policies should grant permissions to groups rather than individual users. Granting permissions directly to users bypasses group-based access control, making it harder to audit and manage access at scale

Risk Level

Medium

Address

Compliance, Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • AWS Startup Security Baseline
  • 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
  • HITRUST CSF
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST
  • NIST CSF
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • PCI
  • Reserve Bank of India (RBI) Cyber Security Framework
  • Reserve Bank of India (RBI) Master Direction – Information Technology Framework
  • SOC2
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Using Console

Below are concise, console-based steps to remediate “OCI IAM Policies Should Grant Permissions To Groups Not Users” for IAM (including Monitoring) in OCI.

1. Identify policies that reference users directly

  1. Sign in to the OCI Console.
  2. Open the Navigation MenuIdentity & SecurityIdentityPolicies.
  3. Select the relevant Compartment (top-left compartment picker).
  4. For each policy:
    • Click the policy name.
    • Check the Statements section for lines like:
      • Allow user <username> to manage monitoring-family in compartment <compartment-name>
      • or any Allow user <username> ...

These are the ones to fix.


2. Create a group to replace direct user grants (if not already present)

If you don’t already have a suitable group (e.g., “Monitoring-Admins” or “Monitoring-Users”):

  1. Navigation MenuIdentity & SecurityIdentityGroups.
  2. Click Create Group.
  3. Enter:
    • Name: e.g., monitoring-admins
    • Description: e.g., Group for users who manage OCI Monitoring
  4. Click Create.

3. Add users (who had direct policy grants) into the group

  1. Still under Groups, click the group you created (e.g., monitoring-admins).
  2. Go to the Users tab.
  3. Click Add User to Group.
  4. Select each user that previously appeared in user-specific policy statements.
  5. Click Add.

Repeat until all affected users are included.


4. Update the policy statements to use the group instead of users

For each policy that referenced users directly:

  1. Navigation MenuIdentity & SecurityIdentityPolicies.

  2. Select the compartment, then click the relevant policy.

  3. Click Edit Policy Statements (or Edit).

  4. In the statements, replace lines like:

    • From:
      Allow user alice to manage monitoring-family in compartment Prod
    • To:
      Allow group monitoring-admins to manage monitoring-family in compartment Prod

    Do this for all statements that use user <username>; convert them to group <group-name> with equivalent verbs (inspect/read/use/manage) and resources (monitoring-family, alarms, metrics, etc.).

  5. Click Save Changes.


5. Remove or clean up any leftover user-based policies

  • Ensure no policies (in any compartment or the root compartment) still contain Allow user <username> ... unless there is a very specific, justified exception.
  • If you find leftover policies that only granted permissions to a user and have now been migrated to group-based statements, you can:
    • Remove the user-specific statements, or
    • Delete the entire policy if it is no longer needed.

6. (Optional) Verify Monitoring access via console

Have a member of the group:

  1. Sign in to the console.
  2. Go to Observability & ManagementMonitoringService Metrics or Alarms.
  3. Confirm they can perform the expected actions (view, create, edit alarms, etc.) appropriate to the level of access you granted.

If you share an example of an existing user-based policy statement, I can give you the exact group-based replacement text for Monitoring.

Using CLI

To remediate “OCI IAM Policies should grant permissions to groups, not users” using OCI CLI, you basically need to:

  1. Find policies that reference specific users in their statements.
  2. Create/choose an IAM group.
  3. Add those users to the group.
  4. Create equivalent policies for the group.
  5. Remove or edit user-targeting policies.

Below is a minimal, step‑by‑step CLI workflow.


0. Prerequisites

  • OCI CLI installed and configured (oci setup config done).
  • You know:
    • Your tenancy OCID
    • The compartment OCID(s) where the policies live (root or sub‑compartment).

1. List Policies and Identify User-Based Statements

List policies in a compartment (root or otherwise):

oci iam policy list \
--compartment-id <COMPARTMENT_OCID> \
--all

For each policy OCID, get the details:

oci iam policy get --policy-id <POLICY_OCID> --query 'data."statements"[]' --raw-output

Look for statements like:

Allow user <user_name> to <verb> <resource-type> in compartment <compartment_name>

or any Allow user ... line. Those are the misconfigurations.


2. Create (or Choose) a Group

If you already have an appropriate group, note its name/OCID and skip this step.

Create a new group:

oci iam group create \
--compartment-id <TENANCY_OCID> \
--name <GROUP_NAME> \
--description "Group to replace user-based IAM policies"

Capture the group OCID from the output (data.id).


3. Add Users to the Group

For every user referenced in Allow user <user_name> ... statements:

  1. Get the user OCID:

    oci iam user list \
    --compartment-id <TENANCY_OCID> \
    --all \
    --query "data[?\"name\"=='<USER_NAME>'].id | [0]" \
    --raw-output
  2. Add that user to the group:

    oci iam group add-user \
    --group-id <GROUP_OCID> \
    --user-id <USER_OCID>

4. Construct Equivalent Group-Based Policy Statements

For each user-based statement:

Allow user <user_name> to <verb> <resource-type> in compartment <compartment_name>

change it to:

Allow group <GROUP_NAME> to <verb> <resource-type> in compartment <compartment_name>

Keep the verbs, resource types, and compartments intact; only swap user <user_name>group <GROUP_NAME>.


5. Create a New Policy for the Group (Preferred)

Instead of editing in place (which can be risky if reused), create a new policy in the same compartment:

  1. Prepare a JSON file group-policy-statements.json:

    {
    "statements": [
    "Allow group <GROUP_NAME> to <verb> <resource-type> in compartment <compartment_name>",
    "Allow group <GROUP_NAME> to <verb> <resource-type2> in compartment <compartment_name2>"
    ]
    }
  2. Create the policy:

    oci iam policy create \
    --compartment-id <COMPARTMENT_OCID> \
    --name <NEW_POLICY_NAME> \
    --description "Replaces user-based policy for OCI IAM/Monitoring access" \
    --statements file://group-policy-statements.json

6. Disable or Delete the Old User-Based Policy

When you confirm everything works (users in the group can still perform required actions), delete or disable the old policy.

  • To delete:

    oci iam policy delete \
    --policy-id <OLD_POLICY_OCID> \
    --force
  • To disable without deleting (edit statements to a no-op or empty set):

    First fetch the full JSON:

    oci iam policy get --policy-id <OLD_POLICY_OCID> > old-policy.json

    Edit statements to an empty list ("statements": []), then update:

    oci iam policy update \
    --policy-id <OLD_POLICY_OCID> \
    --statements file://old-policy.json

(Or better: delete the policy once you are sure you don’t need it.)


7. Verify Effective Access

Use oci as one of the affected users (via a user-specific profile in your ~/.oci/config):

oci iam compartment list --compartment-id <COMPARTMENT_OCID>

Or perform the relevant Monitoring/IAM calls the user normally needs (e.g., access to metrics, alarms, etc.) to ensure access is preserved via the group-based policy.


This process remediates the “user not group” IAM policy finding for OCI IAM/Monitoring using only OCI CLI.

Using Python

Below is a practical way to:

  1. Detect IAM policies that grant permissions directly to users instead of groups, and
  2. Remediate them using Python + OCI SDK (move those permissions to groups and update policies).

1. Conceptual Remediation Steps (OCI Console / API)

  1. Identify bad policies
    Find IAM policies whose statements contain:

    • ALLOW user <name> ... or
    • ALLOW any-user ... (if applicable for your environment).
  2. For each offending statement:

    • Determine the user(s) referenced.
    • Identify an appropriate group (existing or new) that should receive this permission.
    • Add the user to the group (if not already in it).
    • Rewrite the policy statement, replacing:
      • ALLOW user <user-name> ...
        with
        ALLOW group <group-name> ...
    • Apply updated policy.
  3. Optionally:

    • Tag or record which policies were modified.
    • Re-run check to confirm there are no remaining user-based statements.

2. Python Setup

pip install oci

Configure ~/.oci/config (or use instance principal / resource principal).

Example ~/.oci/config:

[DEFAULT]
user=ocid1.user.oc1..aaaa...
fingerprint=xx:xx:...
key_file=/path/to/oci_api_key.pem
tenancy=ocid1.tenancy.oc1..aaaa...
region=eu-frankfurt-1

3. Python Script – Detect and Remediate

Notes/assumptions:

  • This script:
    • Scans all compartments (or a specific root compartment).
    • Looks for policies with ALLOW user.
    • Creates or uses a target group for each user (pattern: user-<username>-group) if no mapping is specified.
    • Adds the user to that group.
    • Rewrites statements accordingly and updates the policy.
  • Adjust naming, compartment scoping, and mapping logic to your environment.
import oci
import re

# --------------- CONFIG ---------------

# Root compartment OCID to start from (usually your tenancy OCID)
ROOT_COMPARTMENT_ID = "ocid1.tenancy.oc1..xxxxx"

# Policy statement regex to find "ALLOW user <name>"
USER_PATTERN = re.compile(r'^\s*ALLOW\s+user\s+(\S+)\s+(.+)$', re.IGNORECASE)

# Optionally, provide an explicit mapping: user -> group
# If not present, script creates "user-<user_name>-group"
EXPLICIT_USER_GROUP_MAP = {
# "alice@example.com": "Admins",
}

# --------------- CLIENT INIT ---------------

config = oci.config.from_file() # or from_file(profile_name="...")

identity = oci.identity.IdentityClient(config)


# --------------- HELPER FUNCTIONS ---------------

def get_all_compartments(root_compartment_id):
"""Return all compartments (active only), including root."""
compartments = []
response = oci.pagination.list_call_get_all_results(
identity.list_compartments,
root_compartment_id,
compartment_id_in_subtree=True,
access_level="ANY"
)
for c in response.data:
if c.lifecycle_state == "ACTIVE":
compartments.append(c)
# include root
root = identity.get_compartment(root_compartment_id).data
compartments.append(root)
return compartments


def get_or_create_group(group_name, description="Group created for IAM remediation"):
"""Return group OCID for given name, creating if needed."""
groups = oci.pagination.list_call_get_all_results(
identity.list_groups,
compartment_id=config["tenancy"]
).data

for g in groups:
if g.name == group_name and g.lifecycle_state == "ACTIVE":
return g.id

# create group
print(f"Creating group: {group_name}")
create_details = oci.identity.models.CreateGroupDetails(
compartment_id=config["tenancy"],
name=group_name,
description=description
)
group = identity.create_group(create_details).data
return group.id


def find_user_by_name(user_name):
"""Find user by 'name' field (NOT description, NOT email)."""
users = oci.pagination.list_call_get_all_results(
identity.list_users,
compartment_id=config["tenancy"]
).data
for u in users:
if u.name == user_name and u.lifecycle_state == "ACTIVE":
return u
return None


def add_user_to_group(user_id, group_id):
"""Add a user to a group if not already in it."""
memberships = oci.pagination.list_call_get_all_results(
identity.list_user_group_memberships,
compartment_id=config["tenancy"],
user_id=user_id
).data

for m in memberships:
if m.group_id == group_id and m.lifecycle_state == "ACTIVE":
return # already a member

print(f"Adding user {user_id} to group {group_id}")
details = oci.identity.models.AddUserToGroupDetails(
user_id=user_id,
group_id=group_id
)
identity.add_user_to_group(details)


def rewrite_policy_statements(statements):
"""
For each 'ALLOW user <user>' statement:
- decide target group
- ensure user is in group
- convert to 'ALLOW group <group>'
Returns (new_statements, changed_flag).
"""
changed = False
new_statements = []

for stmt in statements:
m = USER_PATTERN.match(stmt)
if not m:
new_statements.append(stmt)
continue

user_name, rest = m.groups()
print(f"Found user-based statement: {stmt}")

# find user
user_obj = find_user_by_name(user_name)
if not user_obj:
print(f"WARNING: user '{user_name}' not found, leaving statement unchanged.")
new_statements.append(stmt)
continue

# figure out group name
group_name = EXPLICIT_USER_GROUP_MAP.get(user_name)
if not group_name:
group_name = f"user-{user_name}-group"

group_id = get_or_create_group(group_name)
add_user_to_group(user_obj.id, group_id)

# build new statement
new_stmt = f"ALLOW group {group_name} {rest}"
print(f"Rewriting: '{stmt}' -> '{new_stmt}'")
new_statements.append(new_stmt)
changed = True

return new_statements, changed


# --------------- MAIN REMEDIATION ---------------

def remediate_policies():
compartments = get_all_compartments(ROOT_COMPARTMENT_ID)

for comp in compartments:
print(f"Scanning compartment: {comp.name} ({comp.id})")

policies = oci.pagination.list_call_get_all_results(
identity.list_policies,
compartment_id=comp.id
).data

for policy in policies:
if policy.lifecycle_state != "ACTIVE":
continue

print(f" Checking policy: {policy.name} ({policy.id})")

original_statements = list(policy.statements or [])
new_statements, changed = rewrite_policy_statements(original_statements)

if changed:
print(f" Updating policy: {policy.name}")
update_details = oci.identity.models.UpdatePolicyDetails(
description=policy.description,
statements=new_statements,
version_date=policy.version_date # keep same or set None
)
identity.update_policy(policy.id, update_details)


if __name__ == "__main__":
remediate_policies()

4. How This Ties to “OCI IAM Monitoring”

If you’re feeding this into a monitoring / compliance pipeline:

  • Run this script regularly via:
    • OCI Functions, OCI DevOps Pipeline, or a scheduled job (e.g., cron from an OCI Compute instance).
  • Before remediation, you can:
    • First run it in “report-only” mode (just detect and log without update_policy) by:
      • Removing or gating the identity.update_policy(...) call behind a flag.

If you provide examples of your current policy statements, I can adjust the regex and rewrite logic to match them exactly.

Using Terraform
resource "oci_identity_policy" "iam_monitoring_policy" {
# Replace with your tenancy / compartment OCID
compartment_id = "OCID_OF_TENANCY_OR_COMPARTMENT"

name = "iam-monitoring-policy"
description = "IAM policy for monitoring, granting permissions to groups (not users)."

# Replace GROUP_OCID_* with actual group OCIDs or group names (per your org standard),
# and adjust verbs / resources to match your original user-based policy intent.
statements = [
# Example: previously "Allow user USER_OCID_1 to read metrics in tenancy"
"Allow group GROUP_OCID_1 to read metrics in tenancy",

# Example: previously "Allow user USER_OCID_2 to read alarms in tenancy"
"Allow group GROUP_OCID_2 to read alarms in tenancy",

# Add additional statements as needed, all targeting 'group' instead of 'user'
# "Allow group GROUP_OCID_3 to manage alarms in compartment COMPARTMENT_NAME",
]
}

Changing statements on oci_identity_policy updates the policy in place and does not force resource replacement, but it does immediately change authorization behavior when applied.

Verification with terraform plan should show the existing oci_identity_policy with its statements argument changing from Allow user ... entries to the new Allow group ... entries and no -/+ replacement of the resource itself.