How to onboard a new Customer on Oracle Cloud Infrastructure with Terraform
In this article we will outline how you can manage Identity Service resources in an existing Oracle Cloud Infrastructure Tenancy using Terraform, allowing you to onboard new customers by managing complex organizations and rules with logical groups of users and resources, and also, to define polices so that you can control who has access to your cloud resources, what type of access they have, and to which specific resources.
You can think about some use cases, for example, an ISV organization deploying their N-tier application for multiple customers on a shared environment (tenancy), where each customer only have access to its own set of infrastructure resources, completely secured and isolated from other customers. Or you can have a large Enterprise managing multiple projects and teams working on a shared environment (tenancy) with different access to their resources.
Oracle Cloud Infrastructure — Identity and Access Management Service
The Oracle Cloud Infrastructure (OCI) — Identity and Access Management (IAM) Service allows you to securely control access to all of your cloud resources. With the IAM Service, you can leverage a single model for authentication and authorization across all the OCI services. The IAM Service makes it easy to manage access for organizations of all sizes, from one person working on a single project to large companies with many groups working on many projects at the same time, all within a single account. Resource management and authorization can happen at the account level or at the compartment level, while still allowing central auditing and billing.
The IAM Service was built from the ground up to allow you to enforce the security principle of least privilege — by default, new users are not allowed to perform any actions on any resources. Administrators can then grant each user only the access appropriate for that specific user.
There are some key concepts of the IAM Services you need to understand:
- Compartment — A logical container for your cloud resources. Administrators within an account can create one or more compartments to organize and manage resources within their account. You might use compartments to:
- Meter usage separately for distinct business units (e.g., for chargeback purposes)
- Separate your software development environments (e.g., dev, test, production)
- Simplify permission management (allow certain employees full or partial access to specific compartments)
- Minimize the set of resources visible to certain sets of users
- Tenancy (Root compartment) — The top level compartment within your account. The root compartment is created for you automatically when your account is provisioned.
- User — An entity that can be authenticated. A user can be either a person or a machine account. Each user has a unique name in your account and a globally unique identifier. Users can be given passwords to access the web Console and keys to access the services through the APIs.
- Group — A set of users. Groups are used to simplify access management. For example, software developers can be grouped together as members of a “developers” group, which allows them to read, write, and modify code. A single user can be a member of multiple groups.
- Policy — A document that specifies who can access which Oracle BMCS resources your company has, and how. A policy is comprised of policy statements. Multiple policies comprise a policy corpus.
The IAM Service is enabled by default at no additional charge. The very first user in your account is the default administrator. All subsequent users are created through the IAM Service, where you explicitly grant them privileges to interact with specified cloud resources.
What is Terraform?
Terraform is an open source orchestration language and engine for provisioning of cloud infrastructure resources. The Terraform command line tool processes a configuration file representing the desired infrastructure state and applies the required changes to the target environment to create and update the configured resource. By treating “infrastructure-as-code” terraform enables repeatable provisioning that can easily be incorporated in DevOps practices and CICD automation.
Example Scenario
In this scenario, Acme Company will have 2 distinct teams using OBMCS resources for infrastructure: Project1 and Project2. We will create individual Groups for each project and add Policies allowing users to only have Administrative privileges to their own Compartments at project level.
Prerequisites
- Install Terraform
- Setup the Terraform oci provider.
- OCI Account with Administrator privileges (belong to Administrator group)
Setup oci provider
For setting up oci provider, you can follow the official documentation.
After downloading the latest binaries according to your platform, you should extract the files to ~/.terraform.d/plugins/on *nix systems or %APPDATA%/terraform.d/plugins/ on Windows.
Then you need to create a terraform.tfvars file to set your environment specific credentials variables which will contain the following information:
- RSA key pair in PEM format (minimum 2048 bits). See How to Generate an API Signing Key.
- Fingerprint of the public key. See How to Get the Key's Fingerprint.
- Tenancy's OCID and Admin user's OCID. See Where to Get the Tenancy's OCID and User's OCID.
See an example of terraform.tfvars file. Change the values according to your environment:
tenancy_ocid="ocid1.tenancy.oc1..aaaabcaalja2dtxha5xc3kqvyr6yzh5idvt5juh5fezdjtlce4xkt6jkuxab"
user_ocid="ocid1.user.oc1..aaaaaaaa32q63an2635kckababfrjso5iney7s6v33lablcawxzseapobb7q"
fingerprint="a9:8f:b3:b8:5e:3b:71:57:9b:3c:5f:c5:69:06:1f:75"
private_key_path="~/.ssh/id_rsa"
region="us-phoenix-1"Usage
On this particular example, I created multiple terraform template files to manage each resource separately with all the dependencies. If you want to, you can put them into a single file. But this is a design decision, I would rather building multiple small files with few lines of code instead of one large single file with hundreds of lines of code.
Let’s check the details of every Terraform template file we created on this article:
variables.tf - Contains the declaration of Terraform provider environment variables and any template input variables. We will have a user_count variable which basically determines the number of Groups and Compartments in addition to the users, since we have a 1:1 mapping on our example. Observe that we specified a default value for user_count. We can override its value on terraform.tfvars.
# specify input variablesvariable "tenancy_ocid" {}
variable "user_ocid" {}
variable "fingerprint" {}
variable "private_key_path" {}
variable "region" {}#On this particular sample code, we are expecting a 1:1 mapping between Users x Groups x Compartments.
# For every User, we will create a Group and a Compartment.
# You need to modify the template files to accommodate a different mapping/relationship.variable "user_count" {
default = 2
}
main.tf - Main terraform template file initializing all the provider environment variables.
# initialize provider with environment variablesprovider "oci" {
tenancy_ocid = "${var.tenancy_ocid}"
user_ocid = "${var.user_ocid}"
fingerprint = "${var.fingerprint}"
private_key_path = "${var.private_key_path}"
region = "${var.region}"
}
compartments.tf - Compartment resource template file.
# Manage Compartment resource.
# Name format: ProjectXXX'
# where XXX is the number of Compartments incremented by 1,
# total is specified by the variable count ${var.count}.resource "oci_identity_compartment" "compartments" {
count = "${var.user_count}"
name = "${format("Project%03d", count.index + 1)}"
description = "Compartment for user ${format("User%03d", count.index + 1)}"
}
groups.tf - Group resource template file.
# Manage Group resource.
# Name: 'ProjectXXX'
# where XXX is the number of groups incremented by 1,
# total is specified by the variable count ${var.count}.resource "oci_identity_group" "groups" {
count = "${var.user_count}"
name = "${format("Project%03d", count.index + 1)}"
description = "Group - ${format("Project%03d", count.index + 1)} "
}
users.tf - User resource template file.
# Manage users and create one-time credentials for UI
# Name: UserXXX
# where XXX is the number of groups incremented by 1,
# total is specified by the variable count ${var.count}.resource "oci_identity_user" "users" {
count = "${var.user_count}"
name = "${format("User%03d", count.index + 1)}"
description = "User - ${format("User%03d", count.index + 1)}"
}resource "oci_identity_ui_password" "user_pwd" {
count = "${var.user_count}"
user_id = "${element(oci_identity_user.users.*.id, count.index + 1)}"
}output "users_temp_credentials" {
sensitive = false
value = "${formatlist("User: %v Password: %v", oci_identity_user.users.*.name, oci_identity_ui_password.user_pwd.*.password)}"
#value = "${join(" \n ",oci_identity_ui_password.user_pwd.*.password)}"
}
group_membership.tf - User x Group resources template file.
# Manage Membership.
# Add user to group and compartmentresource "oci_identity_user_group_membership" "group_membership" {
depends_on = ["oci_identity_compartment.compartments", "oci_identity_user.users", "oci_identity_group.groups"]
count = "${var.user_count}" compartment_id = "${element(oci_identity_compartment.compartments.*.id, count.index + 1)}"
user_id = "${element(oci_identity_user.users.*.id, count.index + 1)}"
group_id = "${element(oci_identity_group.groups.*.id, count.index + 1)}"
}
policies.tf - Policy resource template file.
# Manage policies
# On this particular sample code, we are expecting a one-to-one mapping between Users x Groups x Compartments.
# This is why we have one unique variable `count` for Group, Compartment and Users.
# If you want to have different number of resources depending on the type, you may have to modify the
# statements according to your need.resource "oci_identity_policy" "policies" {
depends_on = ["oci_identity_compartment.compartments", "oci_identity_user.users", "oci_identity_group.groups", "oci_identity_user_group_membership.group_membership"]
count = "${var.user_count}" name = "${format("PolicyForUser%03d", count.index + 1)}"
description = "${format("Policy For User%03d", count.index + 1)}"
compartment_id = "${var.tenancy_ocid}" statements = ["Allow any-user to manage all-resources in compartment ${format("Project%03d", count.index + 1)} where request.user.id='${element(oci_identity_user.users.*.id, count.index)}'",
"${format("Allow group Project%03d", count.index + 1)} to manage all-resources in compartment ${format("Project%03d", count.index + 1)}",
]
}
Apply the configuration
First review the configuration plan of the resources that will be created.
$ terraform planThen apply the configuration.
$ terraform applyAs a result, Terraform will create all the resources on your environment and output the one-time credentials for the users specified on the users.tf file.
Clean-up
To destroy the resources
$ terraform destroy