A Step-by-Step Guide to Building Terraform Access Management That Scales Beyond Manual Provisioning
The Access Management Problem
At 20 employees, you onboard 2–3 people monthly. At 50+ employees, you’re processing 8–12 new hires plus departures and role changes.
Security team spends 4 hours onboarding each new employee across 15+ SaaS applications. When someone leaves, you find their AWS account still active 3 weeks later. Maya Kaczorowski’s research of 50+ security leaders confirms access management is the top security issue organizations face.
Manual user provisioning creates three critical problems:
- Time waste: IT teams spend 20+ hours weekly processing access requests, creating accounts, and managing permissions across multiple systems.
- Security gaps: Departing employees retain access to production systems for days or weeks. Former contractors still have GitHub repository access months after project completion.
- Compliance failures: No centralized audit trail exists for access changes. Regulatory audits reveal inconsistent permission assignments and missing documentation.
The business cost: A 200-person company wastes $50,000 annually on manual access management. Each security incident from stale access costs an average of $15,000 in incident response time.
Solution Architecture Overview
The solution treats user access as code that can be automated, versioned, and audited. Infrastructure as Code manages user accounts across SaaS platforms through standardized APIs and configuration files.
Access management architecture layers:
- Identity Layer: Standardized user identities and roles across all systems. Every user follows consistent naming conventions (first.last) and email formats (first.last@company.com).
- Policy Layer: Role-based access control definitions and permissions stored as code. Teams like “DevOps” and “Security” have predefined permission sets that apply across all connected systems.
- Provisioning Layer: Automated account creation and access assignment through API calls. New user addition to configuration file triggers account creation across all designated platforms.
- Governance Layer: Approval workflows and compliance controls built into version control. All access changes require code review and approval before execution.
- Audit Layer: Complete change tracking and access reviews through Git history. Every permission change has timestamps, author attribution, and approval records.
- Integration Layer: API connections to SaaS applications handle actual provisioning operations. Terraform providers communicate with AWS IAM, GitLab, and other platforms.
Why Terraform fits this architecture: Terraform manages infrastructure through declarative configuration and maintains state consistency across multiple providers. Its extensive provider ecosystem covers most SaaS applications your organization uses.
Expected measurable outcomes:
- Reduce onboarding time from 4 hours to 15 minutes
- Eliminate access gaps through automated offboarding
- Provide complete audit trail for compliance requirements
- Scale access management without additional IT staff
Technical Foundation & Standards
Identity standardization requirements: All user accounts must follow consistent patterns to enable automation across platforms.
# Required naming conventions
username: first.last
email: first.last@company.com
full_name: "First Last"
Data modeling approach: YAML files serve as single source of truth for all access decisions. Two primary files control the entire system:
rbac.yaml — Role and permission definitions:
aws:
groups:
- name: DevOps
policies: [arn:aws:iam::aws:policy/AdministratorAccess]
- name: Security
policies: [arn:aws:iam::aws:policy/SecurityAudit]
- name: Developer
policies: [arn:aws:iam::aws:policy/AmazonS3FullAccess]
gitlab:
groups: ["devops", "security", "product"]
users.yaml — Individual user configurations:
users:
- username: "john.doe"
gitlab:
group: ["devops::maintainer", "product"]
state: active
aws:
path: "/"
group: [DevOps]
user_info:
name: "John Doe"
email: "john.doe@example.com"
team: devops
Project organization and file structure:
access_management/
├── data/
│ ├── rbac.yaml # Permission definitions
│ └── users.yaml # User configurations
├── modules/
│ ├── aws/ # AWS IAM management
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── providers.tf
│ └── gitlab/ # GitLab user management
│ ├── main.tf
│ ├── groups.tf
│ └── variables.tf
├── main.tf # Root configuration
└── variables.tf # Global variables
RBAC definitions and user mappings: Each user maps to specific groups within each platform. Groups contain predefined permissions that apply consistently across environments.
Implementation Deep Dive
Complete code walkthrough: The system processes YAML configuration files through Terraform modules that call provider APIs to create and manage user accounts.
AWS IAM module breakdown (line-by-line analysis):
# modules/aws/main.tf
locals {
# Filter users to only those with AWS configurations
aws_user = { for user in var.users : user["username"] => user if contains(keys(user), "aws") }
}
resource "aws_iam_user" "user" {
for_each = local.aws_user
name = each.value["username"]
path = lookup(each.value["aws"], "path", "/")
tags = lookup(each.value, "tags", {})
permissions_boundary = lookup(each.value["aws"], "permissions_boundary", null)
}
resource "aws_iam_user_login_profile" "user_login_profile" {
for_each = local.aws_user
user = aws_iam_user.user[each.value["username"]].name
password_reset_required = true
}
resource "aws_iam_user_group_membership" "membership" {
for_each = local.aws_user
groups = each.value["aws"].group
user = each.value.username
depends_on = [aws_iam_user.user]
}
Technical breakdown:
- Line 4–6:
local.aws_userfilters input data to users with AWS configuration blocks - Line 8–14:
aws_iam_userresource creates IAM users with configurable paths and permission boundaries - Line 16–20:
aws_iam_user_login_profileenables console access with mandatory password reset - Line 22–28:
aws_iam_user_group_membershipassigns users to groups defined in YAML configuration
Main configuration and data loading:
# main.tf
locals {
users = yamldecode(file("${path.module}/data/users.yaml"))
rbac = yamldecode(file("${path.module}/data/rbac.yaml"))
provider_creds = jsondecode(data.aws_secretsmanager_secret_version.credentials.secret_string)
}
data "aws_secretsmanager_secret_version" "credentials" {
secret_id = var.secret_manager_creds_name
}
module "provision_users_aws" {
source = "./modules/aws"
users = local.users["users"]
rbac = local.rbac["aws"]["groups"]
}
How the pieces connect together:
- YAML files define desired state for users and permissions
- Main configuration loads YAML data and credentials from AWS Secrets Manager
- Terraform modules receive user data and execute API calls to create accounts
- State file tracks current infrastructure status and detects configuration drift
GitLab integration specifics:
# Example GitLab user provisioning
resource "gitlab_user" "employee" {
username = each.value["username"]
email = each.value["user_info"]["email"]
name = each.value["user_info"]["name"]
state = each.value["gitlab"]["state"]
}
Production Deployment Architecture
State management strategy: Remote state storage prevents concurrent modifications and provides centralized state access for team members.
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "access-management/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
CI/CD pipeline design and security: GitLab CI/CD automates Terraform execution with proper approval controls.
# .gitlab-ci.yml
stages:
- plan
- apply
terraform_plan:
stage: plan
script:
- terraform plan -out=plan.tfplan
artifacts:
paths:
- plan.tfplan
terraform_apply:
stage: apply
script:
- terraform apply plan.tfplan
when: manual
only:
- main
Credential management approach: AWS Secrets Manager stores all SaaS application API tokens with encryption at rest.
{
"gitlab": {
"access_token": "glpat-xxxxxxxxxxxx"
},
"aws": {
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
}
Monitoring, logging, and alert configuration: CloudTrail logs all API calls made by Terraform. GitLab CI/CD provides execution logs for all access changes.
Get Muh. Fani Akbar’s stories in your inbox
Join Medium for free to get updates from this writer.
Subscribe
Subscribe
Environment separation strategy: Separate Terraform workspaces for staging and production environments prevent accidental changes to production access.
terraform workspace new staging
terraform workspace new production
terraform workspace select production
Example Manual Operational Execution
Step-by-step implementation process:
- Clone the repository and configure AWS credentials
- Initialize Terraform backend:
terraform init - Create workspace:
terraform workspace new production - Verify configuration:
terraform plan - Apply changes:
terraform apply
Adding new users (concrete example):
Add user configuration to data/users.yaml:
users:
- username: "jane.smith"
gitlab:
group: ["security::maintainer"]
state: active
aws:
path: "/"
group: [Security]
user_info:
name: "Jane Smith"
email: "jane.smith@example.com"
team: security
Plan and apply workflow:
# Review changes before applying
terraform plan
# Output shows:
# + aws_iam_user.user["jane.smith"] will be created
# + aws_iam_user_login_profile.user_login_profile["jane.smith"] will be created
# + gitlab_user.employee["jane.smith"] will be created
# Apply changes
terraform apply
Verification and troubleshooting: Check AWS IAM console and GitLab admin panel to verify account creation. Terraform state file contains resource IDs for tracking.
This operational process reduces user provisioning from 4 hours to 15 minutes while maintaining complete audit trail.
Current System Limitations
What this approach doesn’t solve yet:
Password distribution problem: AWS IAM requires manual password sharing since it cannot send password reset emails directly to users. This creates security risk and manual overhead.
SSO integration gaps: Users still manage separate passwords for each application. Single Sign-On integration would eliminate this friction.
Real-time access reviews: Current system requires manual periodic reviews to identify stale permissions. Automated access certification would improve security.
Internal application coverage: Custom applications without Terraform providers need manual access management. Custom provider development would extend automation coverage.
Specific improvement areas with technical reasoning:
- Implement SSO with Keycloak or Okta: Centralized authentication eliminates password distribution and reduces security risks
- Add compliance automation: Policy validation tools can prevent misconfigurations before they reach production
- Build monitoring dashboard: Real-time access tracking identifies permission drift and security anomalies
- Create custom providers: Internal applications need API-based user management for complete automation
Clear roadmap for Parts 2 and 3:
Part 2: Integrating Terraform with GitLab CI/CD covers automated pipeline setup, approval workflows, and secure credential management in production environments.
Part 3: Integrating Terraform with SSO systems explains Keycloak and Okta integration for centralized authentication and automated role mapping.
Full implementation code available at: https://github.com/mfakbar127/terraform-central-user-access-management
This foundation eliminates manual access management overhead while providing complete audit capability for compliance requirements.




Top comments (0)