Introduction
Managing cloud users manually is error-prone and slow. In enterprise environments, new developers, data engineers, and ops staff need access on day one — with the right permissions, no more and no less. Doing this by clicking through the OCI Console works for one user. It breaks for ten. It fails for a hundred.
This blog walks through a Python automation script that provisions a complete OCI IAM identity: creates a user, creates a group, assigns the user to that group, attaches an IAM policy, and sets a console password — all from a single function call. Everything sensitive lives in a .env file. The private key stays local.
By the end, you will understand not just how to run the code, but why OCI IAM works the way it does.
What Is OCI IAM?
Oracle Cloud Infrastructure Identity and Access Management (IAM) is the security backbone of every OCI tenancy. It answers two fundamental questions:
- Who are you? — Authentication, verifying identity
- What can you do? — Authorization, enforcing permissions
OCI IAM is not a bolt-on feature. Every API call, every console action, every SDK operation passes through IAM. If IAM does not explicitly allow an action, it is denied. This is the default-deny model and it is a cornerstone of cloud security.
Here are the core concepts you need to know before writing a single line of code:
ConceptWhat It MeansTenancyYour top-level OCI account. Think of it as your organisation's root. All resources live inside it.UserAn individual human identity. Users authenticate to the Console or API and carry their own credentials.GroupA named collection of users. Permissions are granted to groups, not directly to users.CompartmentA logical container for OCI resources. You can nest compartments to mirror your org structure (dev / staging / prod).PolicyA set of human-readable statements that grant a group permission to perform actions on resources in a compartment.
What Is an OCI Policy?
A Policy is the unit of authorisation in OCI. It is a collection of statement strings written in plain English following a fixed syntax:
Allow <subject> to <verb> <resource-type> in <location>Examples:
Allow group dev-team to manage instances in compartment dev
Allow group data-engineers to read object-family in tenancy
Allow group auditors to inspect all-resources in tenancyThe four verbs follow a hierarchy of increasing power:
inspect— list resources and metadata, nothing elseread— inspect + read contentuse— read + perform actions that don't create or destroymanage— full control including create, update, and delete
Key Rule: Users have no permissions by default. Even a user in a group has zero access until a policy explicitly grants it. This is least privilege by design.
Project Overview
This project automates the full IAM onboarding workflow using the oci Python SDK. The goal is to replace manual Console clicks with a repeatable, auditable script that can be plugged into any CI/CD pipeline or internal developer portal.
What the script does:
- Reads OCI credentials securely from a
.envfile - Creates a new IAM user in the tenancy
- Creates a new IAM group
- Adds the user to the group
- Creates an IAM policy and attaches it to the compartment
- Sets an initial console password for the user
Project structure:
oci-iam-onboarding/
├── onboard.py # main automation script
├── .env # your credentials (never commit this)
├── .env.example # safe template to share
├── oci_api_key.pem # your OCI API private key
└── requirements.txt # oci, python-dotenvPrerequisites:
- Python 3.10+
- An OCI account with IAM permissions (manage users, groups, policies)
- An OCI API signing key pair (generate in Console → User Settings → API Keys)
pip install oci python-dotenv
Credential Setup
All sensitive values — OCIDs, fingerprint, region — are stored in a .env file. The private key (.pem) sits in the same directory and is referenced by path. Neither file should ever be committed to version control.
.env.example
OCI_USER_OCID=ocid1.user.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OCI_KEY_FILE=./oci_api_key.pem
OCI_FINGERPRINT=xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
OCI_TENANCY_OCID=ocid1.tenancy.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OCI_REGION=ap-mumbai-1
OCI_COMPARTMENT_OCID=ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxCopy .env.example to .env, fill in your real values, and add both .env and *.pem to your .gitignore immediately.
Loading credentials in Python:
import oci, os
from dotenv import load_dotenv
load_dotenv()
config = {
"user": os.getenv("OCI_USER_OCID"),
"key_file": os.getenv("OCI_KEY_FILE"),
"fingerprint": os.getenv("OCI_FINGERPRINT"),
"tenancy": os.getenv("OCI_TENANCY_OCID"),
"region": os.getenv("OCI_REGION"),
}
identity_client = oci.identity.IdentityClient(config)The oci.identity.IdentityClient is the single object through which all IAM operations flow. It handles request signing, retries, and endpoint resolution automatically.
Core Functions
Below are the essential functions that power the automation. Each wraps a single OCI SDK call and returns the response object so callers can chain operations.
1. create_user()
Creates a new IAM user. Note that users always belong to the root tenancy compartment — this is an OCI constraint, not a script choice.
def create_user(name: str, description: str):
details = oci.identity.models.CreateUserDetails(
compartment_id=config["tenancy"],
name=name,
description=description,
)
user = identity_client.create_user(details).data
print(f"[+] User created: {user.name} (OCID: {user.id})")
return user2. create_group()
Creates a named group. Groups are the bridge between users and policies. You never grant permissions directly to a user in OCI — always through a group.
def create_group(name: str, description: str):
details = oci.identity.models.CreateGroupDetails(
compartment_id=config["tenancy"],
name=name,
description=description,
)
group = identity_client.create_group(details).data
print(f"[+] Group created: {group.name} (OCID: {group.id})")
return group3. add_user_to_group()
Links a user OCID to a group OCID. This is the membership record — remove it to instantly revoke all group permissions from a user without deleting anything.
def add_user_to_group(user_id: str, group_id: str):
details = oci.identity.models.AddUserToGroupDetails(
user_id=user_id,
group_id=group_id,
)
identity_client.add_user_to_group(details)
print(f"[+] User added to group")4. create_policy()
This is the most important function. It takes a list of policy statement strings and attaches them to a compartment. Statements must follow the OCI policy syntax exactly.
def create_policy(name: str, description: str,
statements: list[str], compartment_id: str):
details = oci.identity.models.CreatePolicyDetails(
compartment_id=compartment_id,
name=name,
description=description,
statements=statements,
)
policy = identity_client.create_policy(details).data
print(f"[+] Policy created: {policy.name}")
return policy5. create_console_password()
Sets the initial OCI Console UI password for the user. In production, combine this with requiring a password reset on first login.
def create_console_password(user_id: str, password: str):
details = oci.identity.models.CreateOrResetUIPasswordDetails(
password=password,
)
identity_client.create_or_reset_ui_password(user_id, details)
print(f"[+] Console password set")The Orchestrator Function
The onboard_user() function ties everything together. It is the single entry point — whether called from a script, a webhook, or a pipeline — to fully provision an identity.
def onboard_user(username, description, group_name,
group_description, policy_name,
policy_statements, console_password):
user = create_user(username, description)
group = create_group(group_name, group_description)
add_user_to_group(user.id, group.id)
create_policy(policy_name, f"Policy for {group_name}",
policy_statements, COMPARTMENT_ID)
create_console_password(user.id, console_password)
print(f" User : {user.name}")
print(f" Group : {group.name}")
print(f" Policy: {policy_name}")Sample call:
onboard_user(
username="john.doe",
description="New developer — onboarded via automation",
group_name="dev-team",
group_description="Development team group",
policy_name="dev-team-policy",
policy_statements=[
f"Allow group dev-team to manage instances in compartment id {COMPARTMENT_ID}",
f"Allow group dev-team to read buckets in compartment id {COMPARTMENT_ID}",
],
console_password="Welcome@OCI#2025",
)Security Best Practices
Automation is powerful. Automation with misconfigured credentials is dangerous. Follow these before running this in any real environment:
- Never commit
.envor.pemfiles — add both to.gitignoreimmediately - Use a dedicated IAM user for automation, not your personal admin account
- Grant the automation user only the permissions it needs — not
manage all-resources - Rotate the API signing key regularly and revoke old keys
- In production, integrate with OCI Vault to store secrets instead of a
.envfile - Force password reset on first login for any user created programmatically
- Log all onboarding events to OCI Audit or an internal SIEM
Extending This Script
The functions here are deliberately modular. Natural next steps to build on this foundation:
- Offboarding — add a
remove_user()that deletes group memberships, API keys, and then the user - Bulk onboarding — read a CSV of new hires and loop through
onboard_user() - Slack/email notifications — call a webhook after each successful onboarding
- OCI Vault integration — replace
.envsecrets withoci.vault.SecretsClientcalls - REST API wrapper — expose
onboard_user()as a FastAPI endpoint for your internal developer portal
Conclusion
OCI IAM is the foundation of every secure deployment on Oracle Cloud. Understanding its model — tenancy, compartments, users, groups, and policies — is essential before writing a single line of cloud code.
The Python OCI SDK makes automation straightforward. With a clean .env-based credential setup, modular functions, and a single orchestrator, you can go from a new hire email to a fully provisioned cloud identity in seconds — and do it the same way every single time.
This pattern scales. Whether you are onboarding 1 developer or 100, the process is identical, auditable, and repeatable. That is the value of infrastructure automation.