DEV Community

Cover image for Most Security Teams Can’t Scale Access Management Beyond 50 Employees - Here’s how to fix it
Muh. Fani "Rama" Akbar
Muh. Fani "Rama" Akbar

Posted on

Most Security Teams Can’t Scale Access Management Beyond 50 Employees - Here’s how to fix it

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.

Man in shock

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.

Top security issue from CISO interview

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"
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Data flow diagram from YAML configuration files through Terraform modules to SaaS provider APIs

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]
}
Enter fullscreen mode Exit fullscreen mode

Technical breakdown:

  • Line 4–6: local.aws_user filters input data to users with AWS configuration blocks
  • Line 8–14: aws_iam_user resource creates IAM users with configurable paths and permission boundaries
  • Line 16–20: aws_iam_user_login_profile enables console access with mandatory password reset
  • Line 22–28: aws_iam_user_group_membership assigns 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"]
}
Enter fullscreen mode Exit fullscreen mode

How the pieces connect together:

  1. YAML files define desired state for users and permissions
  2. Main configuration loads YAML data and credentials from AWS Secrets Manager
  3. Terraform modules receive user data and execute API calls to create accounts
  4. 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"]
}
Enter fullscreen mode Exit fullscreen mode

Production Deployment Architecture

High level 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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Example Manual Operational Execution

Step-by-step implementation process:

  1. Clone the repository and configure AWS credentials
  2. Initialize Terraform backend: terraform init
  3. Create workspace: terraform workspace new production
  4. Verify configuration: terraform plan
  5. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Implement SSO with Keycloak or Okta: Centralized authentication eliminates password distribution and reduces security risks
  2. Add compliance automation: Policy validation tools can prevent misconfigurations before they reach production
  3. Build monitoring dashboard: Real-time access tracking identifies permission drift and security anomalies
  4. 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)