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
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
- Sign in to the OCI Console.
- Open the Navigation Menu → Identity & Security → Identity → Policies.
- Select the relevant Compartment (top-left compartment picker).
- 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”):
- Navigation Menu → Identity & Security → Identity → Groups.
- Click Create Group.
- Enter:
- Name: e.g.,
monitoring-admins - Description: e.g.,
Group for users who manage OCI Monitoring
- Name: e.g.,
- Click Create.
3. Add users (who had direct policy grants) into the group
- Still under Groups, click the group you created (e.g.,
monitoring-admins). - Go to the Users tab.
- Click Add User to Group.
- Select each user that previously appeared in user-specific policy statements.
- 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:
-
Navigation Menu → Identity & Security → Identity → Policies.
-
Select the compartment, then click the relevant policy.
-
Click Edit Policy Statements (or Edit).
-
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 togroup <group-name>with equivalent verbs (inspect/read/use/manage) and resources (monitoring-family,alarms,metrics, etc.). - From:
-
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:
- Sign in to the console.
- Go to Observability & Management → Monitoring → Service Metrics or Alarms.
- 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:
- Find policies that reference specific users in their statements.
- Create/choose an IAM group.
- Add those users to the group.
- Create equivalent policies for the group.
- Remove or edit user-targeting policies.
Below is a minimal, step‑by‑step CLI workflow.
0. Prerequisites
- OCI CLI installed and configured (
oci setup configdone). - 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:
-
Get the user OCID:
oci iam user list \--compartment-id <TENANCY_OCID> \--all \--query "data[?\"name\"=='<USER_NAME>'].id | [0]" \--raw-output -
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:
-
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>"]} -
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.jsonEdit
statementsto 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:
- Detect IAM policies that grant permissions directly to users instead of groups, and
- Remediate them using Python + OCI SDK (move those permissions to groups and update policies).
1. Conceptual Remediation Steps (OCI Console / API)
-
Identify bad policies
Find IAM policies whose statements contain:ALLOW user <name> ...orALLOW any-user ...(if applicable for your environment).
-
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.
-
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.
- Removing or gating the
- First run it in “report-only” mode (just detect and log without
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.