LDAP Authentication with 2FA on Kubernetes

Complete LDAP authentication solution with two-factor authentication, self-service password management, and GitOps capabilities on Amazon EKS

Project Banner

Content Index

Project Overview

This project deploys a complete LDAP authentication solution with two-factor authentication (2FA), self-service password management, and GitOps capabilities on Amazon EKS using Terraform.

Ask the docs

Core Infrastructure

  • EKS Cluster (Auto Mode) with IRSA for secure pod-to-AWS-service authentication
  • VPC with public/private subnets and VPC endpoints for private AWS service access
  • Application Load Balancer (ALB) via EKS Auto Mode for internet-facing access
  • Route53 DNS integration for domain management
  • ACM Certificates for HTTPS/TLS termination

LDAP Stack

  • OpenLDAP Stack with high availability and multi-master replication
  • PhpLdapAdmin web interface for LDAP administration
  • LTB-passwd self-service password management UI

2FA Application

  • Full-stack 2FA application with Python FastAPI backend and static HTML/JS/CSS frontend
  • Dual MFA methods: TOTP (authenticator apps) and SMS (AWS SNS)
  • LDAP integration for centralized user authentication
  • Self-service user registration with email/phone verification and profile state management
  • Admin dashboard for user management, group CRUD operations, and approval workflows
  • User profile management with email change (verification link), phone change (SMS code), and password change for authenticated users
  • Interactive API documentation via Swagger UI and ReDoc (always enabled)

Supporting Infrastructure

  • PostgreSQL (Bitnami Helm chart, ECR images) for user registration data and email verification token storage with persistent EBS-backed storage
  • Redis (Bitnami Helm chart) for SMS OTP code and login challenge storage with TTL-based automatic expiration and shared state across replicas (required; no in-memory fallback)
  • AWS SES for email verification and notifications with IRSA-based access (no hardcoded credentials)
  • AWS SNS for SMS-based 2FA verification (optional, requires SNS VPC endpoint enabled in backend infrastructure)

DevOps & Security

  • ArgoCD (AWS EKS managed service) for GitOps deployments with AWS Identity Center authentication
  • Network Policies for securing pod-to-pod communication with cross-namespace support
  • IRSA (IAM Roles for Service Accounts) for secure AWS API access from pods without credentials
  • VPC Endpoints for private AWS service access (SSM, STS, SNS) without internet exposure
  • Multi-Account Architecture with separated state storage and deployment accounts
  • S3 File-Based Locking for Terraform state management (migrated from DynamoDB)

Key Features

  • EKS Auto Mode: Simplified cluster management with automatic load balancer provisioning and built-in EBS CSI driver
  • Two-Factor Authentication: Full-stack 2FA application with dual MFA methods (TOTP and SMS)
  • Self-Service User Registration: Email and phone verification with profile state management (PENDING β†’ COMPLETE β†’ ACTIVE)
  • Admin Dashboard: User management, group CRUD operations, approval workflows, and user profile management
  • IRSA Integration: Secure AWS API access from pods without hardcoded credentials via OIDC
  • High Availability: Multi-master OpenLDAP replication with persistent storage
  • GitOps Ready: ArgoCD (AWS managed service) for declarative, Git-driven deployments
  • Multi-Account Architecture: Separation of state storage (Account A) and resource deployment (Account B) for enhanced security
  • API Documentation: Interactive Swagger UI and ReDoc always available at /api/docs and /api/redoc (always enabled, not just in debug mode)
  • Secrets Management: Passwords managed via GitHub repository secrets (for CI/CD) or AWS Secrets Manager (for local deployment) with automated retrieval
  • Public ACM Certificates: Uses public ACM certificates (Amazon-issued) with DNS validation for browser-trusted certificates (no security warnings)
  • Route53 Record Module: Dedicated module for Route53 A (alias) records with cross-account support and ALB zone_id mapping
  • Automated Configuration Management: ECR repository name automatically saved to GitHub variables during backend infrastructure provisioning (eliminates manual configuration steps)
  • ArgoCD Status Validation: Automatic validation that ArgoCD capability is "ACTIVE" before deploying applications (prevents deployment failures due to incomplete ArgoCD setup)
  • Role Assumption Script: Convenient scripts/assume-github-role.sh script for switching between AWS account roles (State, Development, Production) when working in the terminal
  • Unique Build Image Tags: Backend and frontend build workflows use commit SHA plus run ID for image tags (<name>-<sha>-<run_id>), push only that tag to ECR (no :latest), and update Helm values; application provisioning reads tags from Helm values
  • ArgoCD Access Entry: ArgoCD Capability uses EKS Access Policy association for cluster admin permissions, ensuring reliable GitOps sync across namespaces and cluster-scoped resources

Getting Started

Prerequisites

  • Two AWS Accounts minimum β€” split architecture. Quick reference:

    Account A holds state, secrets, and DNS. Account B holds infrastructure and ACM certificates (ALB and certificate must be in the same account).

  • GitHub Account and repository fork: ldap-2fa-on-k8s
  • AWS SSO/OIDC configured (see GitHub Repository Configuration)
  • Domain and DNS (Account A): Route53 hosted zone for your domain must exist in the State Account. All DNS records (including ACM validation CNAMEs) are created here.
  • ACM certificates (Account B): Request a public ACM certificate in each Deployment Account. Validation uses CNAME records in Account A's Route53. Certificate must be in ISSUED status and in the same region as the EKS cluster. See Public ACM Certificate Setup and DNS Validation for step-by-step AWS CLI commands.
  • SMS (Account B, when using SMS 2FA): (1) SMS Sandbox: New SNS accounts are in the SMS sandbox. Add and verify destination phone numbers in SNS Console > Text messaging (SMS) > Sandbox destination phone numbers, or request Exit SMS Sandbox for production. See SMS Sandbox. (2) Sender ID: Request an SMS Sender ID in AWS End User Messaging per deployment account and share with SNS. See SMS Sender ID Setup.
  • GitHub Secrets and Variables configured (see Secrets Requirements for complete details):
    • Required Secrets:
      • AWS_STATE_ACCOUNT_ROLE_ARN
      • AWS_PRODUCTION_ACCOUNT_ROLE_ARN
      • AWS_DEVELOPMENT_ACCOUNT_ROLE_ARN
      • AWS_ASSUME_EXTERNAL_ID
      • TF_VAR_OPENLDAP_ADMIN_PASSWORD
      • TF_VAR_OPENLDAP_CONFIG_PASSWORD
      • TF_VAR_REDIS_PASSWORD
      • TF_VAR_POSTGRESQL_PASSWORD
      • GH_TOKEN
    • Optional (first admin user seed): When all are set, a one-time Job seeds the first admin so they can log into the 2FA app with the same username/password as LDAP. Set in GitHub Secrets or AWS tf-vars: ADMIN_SEED_USERNAME, ADMIN_SEED_EMAIL, ADMIN_SEED_FIRST_NAME, ADMIN_SEED_LAST_NAME, ADMIN_SEED_PHONE_COUNTRY_CODE, ADMIN_SEED_PHONE_NUMBER. See Secrets Requirements.
    • Required Variables:
      • AWS_REGION
      • BACKEND_PREFIX
      • APPLICATION_INFRA_PREFIX - State file key prefix for application infrastructure (value: application_infra_state/terraform.tfstate)
      • APPLICATION_PREFIX - State file key prefix for application (value: application_state/terraform.tfstate)
    • Auto-generated Variables: (set automatically by workflows/scripts)
      • BACKEND_BUCKET_NAME - Set by Terraform backend state provisioning
      • ECR_REPOSITORY_NAME - Automatically set by backend infrastructure provisioning (eliminates manual configuration; retrieved from Terraform outputs and saved to GitHub repository variables)

    Note on State File Isolation: The APPLICATION_INFRA_PREFIX and APPLICATION_PREFIX variables ensure that infrastructure and application state files are stored separately in the same S3 bucket, preventing state conflicts. Both directories use the same BACKEND_BUCKET_NAME but different keys for complete isolation.

  • For Local Deployment: GitHub CLI (gh), AWS CLI, Terraform, kubectl, and jq installed and configured
  • For Local Deployment: Docker must be installed and running for ECR image mirroring. The scripts/mirror-images-to-ecr.sh script requires Docker to pull images from Docker Hub and push them to ECR. This step is automatically executed by setup-application-infra.sh before Terraform operations.
  • For Local Deployment: AWS Secrets Manager configured with github-role, tf-vars, and external-id secrets (see Secrets Requirements)

For detailed prerequisites and setup: Prerequisites | Secrets Configuration | Secrets Requirements

Deployment Options

Local Deployment

Recommended for: Development, testing, and local experimentation

Step 1: Deploy Terraform Backend State Infrastructure

Deploy the S3 bucket for Terraform state storage:

cd tf_backend_state
./set-state.sh

You can also run from the repo root: ./tf_backend_state/set-state.sh. Both set-state.sh and get-state.sh change to their script directory before running, so Terraform and variables are found either way.

What the script does:

  • Retrieves AWS_STATE_ACCOUNT_ROLE_ARN from AWS Secrets Manager
  • Retrieves AWS_REGION and BACKEND_PREFIX from GitHub repository variables
  • Assumes the IAM role and verifies credentials
  • Checks if infrastructure exists (via BACKEND_BUCKET_NAME variable)
  • If new: Runs Terraform init, validate, plan, and apply to create S3 bucket
  • If exists: Downloads existing state file from S3 (if available)
  • Saves/updates BACKEND_BUCKET_NAME to GitHub repository variables
  • Uploads state file to S3

Alternative: Use ./get-state.sh (or ./tf_backend_state/get-state.sh from repo root) to download existing state file without provisioning

Step 2: Deploy Backend Infrastructure

Deploy VPC, EKS cluster, VPC endpoints, IRSA, and ECR:

cd backend_infra
./setup-backend.sh

What the script does:

  • Prompts for AWS region (us-east-1 or us-east-2) and environment (prod or dev)
  • Retrieves repository variables from GitHub (BACKEND_BUCKET_NAME, BACKEND_PREFIX)
  • Retrieves role ARNs from AWS Secrets Manager (github-role secret)
  • Retrieves ExternalId from AWS Secrets Manager (external-id secret)
  • Assumes State Account role for backend operations
  • Generates backend.hcl from template (if it doesn't exist)
  • Updates variables.tfvars with selected region, environment, deployment account role ARN, and ExternalId
  • Runs Terraform init, workspace select/new, validate, plan, and apply
  • Automatically saves ECR repository name to GitHub repository variable ECR_REPOSITORY_NAME (eliminates manual configuration)
Step 3: Deploy Application Infrastructure

Deploy OpenLDAP stack, ALB, Route53 records, ArgoCD Capability, and StorageClass:

cd application_infra
./setup-application-infra.sh

What the script does:

  • Prompts for AWS region (us-east-1 or us-east-2) and environment (prod or dev)
  • Retrieves repository variables from GitHub (BACKEND_BUCKET_NAME, APPLICATION_INFRA_PREFIX)
  • Mirrors Docker images to ECR (runs scripts/mirror-images-to-ecr.sh before Terraform operations):
    • Checks if images exist in ECR before mirroring (skips if already present)
    • Pulls images from Docker Hub: bitnami/redis:latest, bitnami/postgresql:latest, osixia/openldap:1.5.0
    • Pushes images to ECR with tags: redis-latest, postgresql-latest, openldap-1.5.0 (prevents Docker Hub rate limiting by using ECR images)
    • Uses BACKEND_PREFIX (or backend_infra/backend.hcl) and TERRAFORM_WORKSPACE (or AWS_REGION+ENVIRONMENT) for correct backend_infra state path
    • Uses State Account credentials to fetch ECR URL from backend_infra state
    • Assumes Deployment Account role for ECR operations (with ExternalId)
    • Authenticates Docker to ECR automatically
    • Cleans up local images after pushing to save disk space
    • Requires Docker to be installed and running
    • Requires jq for JSON parsing (with fallback to sed for compatibility)
  • Retrieves role ARNs and ExternalId from AWS Secrets Manager
  • Retrieves OpenLDAP password secrets from AWS Secrets Manager (tf-vars secret) and exports as environment variables:
    • TF_VAR_openldap_admin_password
    • TF_VAR_openldap_config_password
    Terraform writes these to a Kubernetes secret with keys LDAP_ADMIN_PASSWORD and LDAP_CONFIG_PASSWORD (osixia/openldap image).
  • Assumes State Account role for backend operations
  • Exports TERRAFORM_WORKSPACE before sourcing scripts/set-k8s-env.sh so the correct backend_infra state is read
  • Generates backend.hcl from template using APPLICATION_INFRA_PREFIX for state file key (if it doesn't exist)
  • Updates variables.tfvars with selected values
  • Sets Kubernetes environment variables using scripts/set-k8s-env.sh (uses BACKEND_PREFIX and TERRAFORM_WORKSPACE; restores original directory after sourcing)
  • Runs Terraform init, workspace select/new, validate, plan, and apply
Step 4: Build Container Images (Required Before Application Deployment)

Important: Before deploying the application in Step 5, you must run both the 03 - Backend Build and Push (03-backend_build_push.yaml) and 03 - Frontend Build and Push (03-frontend_build_push.yaml) workflows to build and push the container images to ECR. These images are required for ArgoCD to sync the applications or for manual Helm deployment.

Why both are required:

  • Without the backend image, ArgoCD cannot sync the backend application (image pull fails)
  • Without the frontend image, ArgoCD cannot sync the frontend application (image pull fails)
  • Both container images do not exist in ECR until you run the build workflows
  • Without these images, ArgoCD cannot sync the 2FA applications and manual Helm deployment will fail

Build and push both images using GitHub Actions:

  1. Build and push backend image:
    • Go to GitHub β†’ Actions β†’ "03 - Backend Build and Push" (03-backend_build_push.yaml)
    • Click "Run workflow"
    • Select environment (prod or dev)
    • Click "Run workflow"
    • Wait for the workflow to complete
  2. Build and push frontend image:
    • Go to GitHub β†’ Actions β†’ "03 - Frontend Build and Push" (03-frontend_build_push.yaml)
    • Click "Run workflow"
    • Select environment (prod or dev)
    • Click "Run workflow"
    • Wait for the workflow to complete

Note: These workflows can be run in parallel since they modify different files. They will automatically handle git conflicts if both run simultaneously.

Step 5: Deploy Application

Deploy PostgreSQL, Redis, SES, SNS, ArgoCD Applications, and 2FA application (backend/frontend):

cd application
./setup-application.sh

What the script does:

  • Prompts for AWS region (us-east-1 or us-east-2) and environment (prod or dev)
  • Retrieves repository variables from GitHub (BACKEND_BUCKET_NAME, APPLICATION_PREFIX)
  • Retrieves role ARNs and ExternalId from AWS Secrets Manager
  • Retrieves password secrets from AWS Secrets Manager (tf-vars) and exports as environment variables:
    • TF_VAR_postgresql_database_password
    • TF_VAR_redis_password
    • TF_VAR_openldap_admin_password (for backend ldap-admin-secret and optional admin-seed Job)
    • Optional: TF_VAR_admin_seed_* (when ADMIN_SEED_* keys are present in tf-vars; enables first admin user seed)
  • Assumes State Account role for backend operations
  • Generates backend.hcl from template using APPLICATION_PREFIX for state file key (if it doesn't exist)
  • Updates variables.tfvars with selected values
  • Extracts backend and frontend image tags from Helm values (backend/helm/ldap-2fa-backend/values.yaml, frontend/helm/ldap-2fa-frontend/values.yaml) and sets TF_VAR_backend_image_tag and TF_VAR_frontend_image_tag (defaults to latest if missing)
  • Exports TERRAFORM_WORKSPACE, sources scripts/set-k8s-env.sh, then restores current directory so Terraform runs in application/
  • Validates ArgoCD capability status - Ensures ArgoCD capability is "ACTIVE" before proceeding (prevents deployment failures)
  • Runs Terraform init, workspace select/new, validate, plan, and apply
  • References infrastructure outputs from application_infra remote state (including LDAP host, base DN, admin DNβ€”used by the admin-seed Job and not hardcoded)

Note: Application components require infrastructure to be deployed first (Steps 1-3 completed). The script automatically validates that the ArgoCD capability status is "ACTIVE" before proceeding with deployment. If the capability is not ACTIVE, deployment will fail with a clear error message. Backend and frontend image tags are read from Helm values (updated by the build workflows); the build workflows push only the computed tag (no :latest).

First admin seed (optional): If you set all ADMIN_SEED_* keys in tf-vars (or GitHub Secrets), Terraform creates a one-time Job that seeds the first admin user with the same username/password as the LDAP admin, email/phone pre-verified, and membership in the LDAP admins group. See Secrets Requirements and Application README.

Deploying 2FA Applications (Backend and Frontend):

The 2FA application backend and frontend can be deployed via ArgoCD Applications (if enabled in variables.tfvars) or manually using Helm charts. For manual deployment instructions, see Deploying the 2FA Application.

After Step 5: If ArgoCD Applications are enabled, ArgoCD will automatically detect the git changes (from the build workflows in Step 4) and sync the deployments. The container images are already in ECR, so ArgoCD can successfully pull and deploy them.

Manual Deployment of 2FA Applications (Optional)

Prerequisites: Infrastructure must be deployed (Steps 1-3 completed). Required tools: kubectl configured to access your EKS cluster, helm (v3.x), aws CLI, and optionally docker for manual builds.

Overview: The deployment process involves building and pushing Docker images, gathering configuration values, creating namespace, deploying backend and frontend Helm charts, and verifying deployment.

4.1: Build and Push Docker Images

You have two options for building and pushing images:

Option A: Using GitHub Actions (Recommended)

  1. Build and push backend image:
    • Go to GitHub β†’ Actions β†’ "03 - Backend Build and Push" (03-backend_build_push.yaml)
    • Click "Run workflow"
    • Select environment (prod or dev)
    • Click "Run workflow"
    • Wait for the workflow to complete
    • Note the image tag from the workflow output (format: ldap-2fa-backend-<commit-sha>-<run-id>; unique per run to avoid ECR tag conflicts)
  2. Build and push frontend image:
    • Go to GitHub β†’ Actions β†’ "03 - Frontend Build and Push" (03-frontend_build_push.yaml)
    • Click "Run workflow"
    • Select environment (prod or dev)
    • Click "Run workflow"
    • Wait for the workflow to complete
    • Note the image tag from the workflow output (format: ldap-2fa-frontend-<commit-sha>-<run-id>; unique per run to avoid ECR tag conflicts)

The GitHub Actions workflows automatically build Docker images, push to ECR, update Helm values.yaml files with new image tags, and commit changes to the repository.

Option B: Manual Build and Push

# Set variables
export AWS_REGION="us-east-1"
export ECR_REPO_NAME="talo-tf-us-east-1-docker-images-prod"  # Adjust based on your setup
export AWS_ACCOUNT_ID="944880695150"  # Your deployment account ID

# Get ECR login
aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com

# Build and push backend
cd application/backend
docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO_NAME}:ldap-2fa-backend-$(git rev-parse --short HEAD) .
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO_NAME}:ldap-2fa-backend-$(git rev-parse --short HEAD)

# Build and push frontend
cd ../frontend
docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO_NAME}:ldap-2fa-frontend-$(git rev-parse --short HEAD) .
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO_NAME}:ldap-2fa-frontend-$(git rev-parse --short HEAD)

5.2: Gather Required Configuration Values

Get the required values from Terraform outputs. Note: ecr_url comes from backend_infra; route53_domain_name comes from application_infra; most other values from application.

# ECR URL (from backend_infra)
cd backend_infra
ECR_URL=$(terraform output -raw ecr_url 2>/dev/null || echo "")
if [ -z "$ECR_URL" ]; then
  echo "ECR URL not found. Make sure backend_infra has been deployed."
  exit 1
fi

# Domain name (from application_infra)
cd ../application_infra
DOMAIN_NAME=$(terraform output -raw route53_domain_name)

# All other values (from application)
cd ../application
INGRESS_CLASS=$(terraform output -raw alb_ingress_class_name)
ALB_NAME=$(terraform output -raw alb_load_balancer_name 2>/dev/null || echo "")
HOSTNAME="app.${DOMAIN_NAME}"
echo "ECR URL: ${ECR_URL}"
echo "Domain: ${DOMAIN_NAME}"
echo "IngressClass: ${INGRESS_CLASS}"
echo "ALB Name: ${ALB_NAME}"
echo "Hostname: ${HOSTNAME}"

BACKEND_TAG="ldap-2fa-backend-$(git rev-parse --short HEAD)"  # Or use tag from GitHub Actions
FRONTEND_TAG="ldap-2fa-frontend-$(git rev-parse --short HEAD)"  # Or use tag from GitHub Actions
SES_ROLE_ARN=$(terraform output -raw ses_iam_role_arn 2>/dev/null || echo "")
SNS_ROLE_ARN=$(terraform output -raw sns_iam_role_arn 2>/dev/null || echo "")
POSTGRES_HOST=$(terraform output -raw postgresql_host)
REDIS_HOST=$(terraform output -raw redis_host)
REDIS_PORT=$(terraform output -raw redis_port)
SES_SENDER_EMAIL=$(terraform output -raw ses_sender_email)
SMS_SENDER_ID=$(terraform output -raw sms_sender_id 2>/dev/null || echo "2FA")

5.3: Create Namespace

kubectl create namespace 2fa-app

5.4: Deploy Backend Application

Create a values file for the backend deployment:

cat > /tmp/backend-values.yaml <<EOF
# Image configuration
image:
  repository: "${ECR_URL}"
  tag: "${BACKEND_TAG}"

# Ingress configuration
ingress:
  enabled: true
  className: "${INGRESS_CLASS}"
  annotations:
    alb.ingress.kubernetes.io/load-balancer-name: "${ALB_NAME}"
  hosts:
    - host: "${HOSTNAME}"
      paths:
        - path: /api
          pathType: Prefix

# Service account with IRSA annotation
serviceAccount:
  annotations:
    eks.amazonaws.com/role-arn: "${SES_ROLE_ARN}"  # Use SES role (includes SNS if enabled)

# LDAP configuration (when deploying via Terraform/ArgoCD, LDAP host/base DN/admin DN come from application_infra outputs; adjust for manual deployment)
ldap:
  host: "openldap-stack-ha.ldap.svc.cluster.local"
  port: 389
  baseDn: "dc=ldap,dc=talorlik,dc=internal"
  adminDn: "cn=admin,dc=ldap,dc=talorlik,dc=internal"

# Database configuration
database:
  url: "postgresql+asyncpg://ldap2fa:CHANGE_ME@${POSTGRES_HOST}:5432/ldap2fa"
  externalSecret:
    enabled: true
    secretName: "postgresql-secret"
    passwordKey: "password"

# Email configuration
email:
  enabled: true
  senderEmail: "${SES_SENDER_EMAIL}"
  appUrl: "https://${HOSTNAME}"

# SMS configuration (if enabled; direct SMS - no topic)
sms:
  enabled: true  # Set to false if SMS 2FA is not enabled
  awsRegion: "${REGION}"
  senderId: "${SMS_SENDER_ID}"

# Redis configuration (if SMS is enabled)
redis:
  enabled: true  # Set to false if SMS 2FA is not enabled
  host: "${REDIS_HOST}"
  port: ${REDIS_PORT}
  existingSecret:
    enabled: true
    name: "redis-secret"
    key: "redis-password"
EOF

Deploy the backend:

cd application/backend/helm

helm upgrade --install ldap-2fa-backend ldap-2fa-backend \
  --namespace 2fa-app \
  --create-namespace \
  --values /tmp/backend-values.yaml \
                  --wait \
                  --timeout 10m

5.5: Deploy Frontend Application

Create a values file for the frontend deployment:

cat > /tmp/frontend-values.yaml <<EOF
# Image configuration
image:
  repository: "${ECR_URL}"
  tag: "${FRONTEND_TAG}"

# Ingress configuration
ingress:
  enabled: true
  className: "${INGRESS_CLASS}"
  annotations:
    alb.ingress.kubernetes.io/load-balancer-name: "${ALB_NAME}"
  hosts:
    - host: "${HOSTNAME}"
      paths:
        - path: /
          pathType: Prefix
EOF

Deploy the frontend:

cd application/frontend/helm

helm upgrade --install ldap-2fa-frontend ldap-2fa-frontend \
  --namespace 2fa-app \
  --create-namespace \
  --values /tmp/frontend-values.yaml \
  --wait \
  --timeout 10m

5.6: Verify Deployment

# Check pods
kubectl get pods -n 2fa-app

# Check services
kubectl get svc -n 2fa-app

# Check ingress
kubectl get ingress -n 2fa-app

# Check backend logs
kubectl logs -n 2fa-app -l app=ldap-2fa-backend --tail=50

# Check frontend logs
kubectl logs -n 2fa-app -l app=ldap-2fa-frontend --tail=50

# Test backend health endpoint
kubectl run -it --rm test-backend --image=curlimages/curl --restart=Never -- \
  curl -k https://${HOSTNAME}/api/healthz

# Check ALB target health
ALB_ARN=$(aws elbv2 describe-load-balancers --region ${REGION} --query "LoadBalancers[?LoadBalancerName=='${ALB_NAME}'].LoadBalancerArn" --output text)
TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --region ${REGION} --load-balancer-arn ${ALB_ARN} --query "TargetGroups[?contains(TargetGroupName, 'backend')].TargetGroupArn" --output text | head -1)
aws elbv2 describe-target-health --region ${REGION} --target-group-arn ${TARGET_GROUP_ARN}

5.7: Access the Application

Once deployed, access the application at:

  • Frontend: https://${HOSTNAME} (e.g., https://app.talorlik.com)
  • Backend API: https://${HOSTNAME}/api (e.g., https://app.talorlik.com/api)
  • API Documentation: https://${HOSTNAME}/api/docs (Swagger UI)

5.8: Updating Deployments

To update the applications after code changes:

  1. Rebuild and push images (via GitHub Actions or manually)
  2. Update Helm values with new image tags
  3. Upgrade Helm releases:
    # Update backend
    helm upgrade ldap-2fa-backend application/backend/helm/ldap-2fa-backend \
      --namespace 2fa-app \
      --set image.tag="${NEW_BACKEND_TAG}" \
      --reuse-values
    
    # Update frontend
    helm upgrade ldap-2fa-frontend application/frontend/helm/ldap-2fa-frontend \
      --namespace 2fa-app \
      --set image.tag="${NEW_FRONTEND_TAG}" \
                      --reuse-values

5.9: Alternative: Enable ArgoCD Applications

If you prefer GitOps deployment via ArgoCD:

  1. Enable ArgoCD applications in variables.tfvars:
    enable_argocd_apps = true
  2. Configure sync policy (optional):
    argocd_app_sync_policy_automated = true
    argocd_app_sync_policy_prune     = true
    argocd_app_sync_policy_self_heal = true
  3. Apply Terraform changes:
    cd application
    terraform apply
  4. ArgoCD will automatically deploy the applications from Git

For detailed deployment instructions: Deploying the 2FA Application

For backend and frontend documentation: Backend API Documentation | Frontend Application Documentation

Destroying Infrastructure (Local)

⚠️ Warning: Destroy operations are permanent and cannot be undone. Always destroy in reverse order: Application β†’ Application Infrastructure β†’ Backend Infrastructure β†’ State.

  1. Destroy Application:
    cd application
    ./destroy-application.sh

    The script retrieves PostgreSQL, Redis, OpenLDAP admin, and optional admin-seed secrets from AWS Secrets Manager (tf-vars) so Terraform can destroy ldap-admin-secret and admin-seed resources. It will prompt for region and environment, then require confirmation ('yes', then 'DESTROY').

  2. Destroy Application Infrastructure:
    cd application_infra
    ./destroy-application-infra.sh

    Script will prompt for region and environment, then require confirmation ('yes', then 'DESTROY')

  3. Destroy Backend Infrastructure:
    cd backend_infra
    ./destroy-backend.sh

    Script will prompt for region and environment, then require confirmation ('yes', then 'DESTROY')

  4. Destroy State Backend (if needed):
    cd tf_backend_state
    ./get-state.sh  # Download state file first (or ./tf_backend_state/get-state.sh from repo root)
    terraform plan -var-file="variables.tfvars" -destroy -out terraform.tfplan
    terraform apply -auto-approve terraform.tfplan

For detailed local setup instructions: Local Development Setup | Terraform Backend State Local Execution

GitHub Actions Deployment

Recommended for: Production deployments, CI/CD pipelines, automated workflows

Step 1: Deploy Terraform Backend State Infrastructure
  1. Go to GitHub β†’ Actions tab
  2. Select "00 - TF Backend State Provisioning" workflow (00-tfstate_infra_provisioning.yaml)
  3. Click "Run workflow" β†’ "Run workflow"
  4. Monitor the workflow execution

What the workflow does:

  • Uses AWS_STATE_ACCOUNT_ROLE_ARN secret for OIDC authentication
  • Defaults AWS_REGION to us-east-1 when the repository variable is not set
  • Validates Terraform configuration
  • Creates S3 bucket with versioning and encryption
  • Saves bucket name as BACKEND_BUCKET_NAME repository variable
  • Uploads state file to S3
Step 2: Deploy Backend Infrastructure
  1. Go to GitHub β†’ Actions tab
  2. Select "01 - Backend Infra Provisioning" workflow (01-backend_infra_provisioning.yaml)
  3. Click "Run workflow"
  4. Select region (us-east-1: N. Virginia or us-east-2: Ohio)
  5. Select environment (prod or dev)
  6. Click "Run workflow"
  7. Monitor the workflow execution

What the workflow does:

  • Uses AWS_STATE_ACCOUNT_ROLE_ARN for backend state operations
  • Uses environment-specific deployment account role ARN (AWS_PRODUCTION_ACCOUNT_ROLE_ARN or AWS_DEVELOPMENT_ACCOUNT_ROLE_ARN)
  • Uses AWS_ASSUME_EXTERNAL_ID for cross-account role assumption
  • Runs Terraform operations to deploy VPC, EKS, VPC endpoints, IRSA, and ECR
  • Automatically saves ECR repository name to GitHub repository variable ECR_REPOSITORY_NAME (eliminates manual configuration)
Step 3: Deploy Application Infrastructure
  1. Go to GitHub β†’ Actions tab
  2. Select "02 - Application Infra Provisioning" workflow (02-application_infra_provisioning.yaml)
  3. Click "Run workflow"
  4. Select region (us-east-1: N. Virginia or us-east-2: Ohio)
  5. Select environment (prod or dev)
  6. Click "Run workflow"
  7. Monitor the workflow execution

What the workflow does:

  • Uses AWS_STATE_ACCOUNT_ROLE_ARN for backend state operations
  • Uses environment-specific deployment account role ARN
  • Uses AWS_ASSUME_EXTERNAL_ID for cross-account role assumption
  • Exports TERRAFORM_WORKSPACE and BACKEND_PREFIX for set-k8s-env.sh and state path consistency
  • Retrieves repository variables (BACKEND_BUCKET_NAME, APPLICATION_INFRA_PREFIX) for backend state configuration
  • Creates backend.hcl from template using APPLICATION_INFRA_PREFIX for state file key
  • Retrieves OpenLDAP password secrets from GitHub repository secrets
  • Runs Terraform operations to deploy OpenLDAP stack, ALB, Route53 records (for phpldapadmin and ltb-passwd), ArgoCD Capability, and StorageClass
Step 4: Build Container Images (Required Before Application Deployment)
  1. Go to GitHub β†’ Actions tab
  2. Run "03 - Backend Build and Push" workflow (03-backend_build_push.yaml):
    • Select the workflow
    • Click "Run workflow"
    • Select environment (prod or dev)
    • Click "Run workflow"
  3. Run "03 - Frontend Build and Push" workflow (03-frontend_build_push.yaml):
    • Select the workflow
    • Click "Run workflow"
    • Select environment (prod or dev)
    • Click "Run workflow"

What the workflows do:

Both workflows build Docker images, push them to ECR (only the computed image tag is pushed; no :latest tag), update Helm values.yaml files with new image tags, and commit changes to the repository. Application provisioning then reads these tags from Helm values. These workflows can be run in parallel since they modify different files.

Step 5: Deploy Application
  1. Go to GitHub β†’ Actions tab
  2. Select "04 - Application Provisioning" workflow (04-application_provisioning.yaml)
  3. Click "Run workflow"
  4. Select region (us-east-1: N. Virginia or us-east-2: Ohio)
  5. Select environment (prod or dev)
  6. Click "Run workflow"
  7. Monitor the workflow execution

What the workflow does:

  • Uses AWS_STATE_ACCOUNT_ROLE_ARN for backend state operations
  • Uses environment-specific deployment account role ARN
  • Uses AWS_ASSUME_EXTERNAL_ID for cross-account role assumption
  • Exports TERRAFORM_WORKSPACE and BACKEND_PREFIX for state path consistency
  • Extracts backend and frontend image tags from Helm values and sets TF_VAR_backend_image_tag and TF_VAR_frontend_image_tag (so the correct images are deployed; no :latest dependency)
  • Creates backend_infra/backend.hcl from template (needed for remote state access)
  • Creates application_infra/backend.hcl from template (needed for ArgoCD status check and remote state access)
  • Validates ArgoCD capability status - Ensures ArgoCD capability is "ACTIVE" before proceeding (prevents deployment failures)
  • Creates backend.hcl from template using APPLICATION_PREFIX for state file key
  • Retrieves password secrets (PostgreSQL, Redis, OpenLDAP admin) from GitHub repository secrets
  • Sets Kubernetes environment variables using set-k8s-env.sh (from application_infra)
  • Runs Terraform operations to deploy PostgreSQL, Redis, SES, SNS, ArgoCD Applications, and Route53 record for 2FA app
  • When optional ADMIN_SEED_* secrets are set, a one-time Job seeds the first admin user
  • LDAP connection settings for the admin-seed Job are read from application_infra remote state (not hardcoded)
Destroying Infrastructure (GitHub Actions)

⚠️ Warning: Destroy operations are permanent and cannot be undone. Always destroy in reverse order: Application β†’ Application Infrastructure β†’ Backend Infrastructure β†’ State.

Confirmation required: Each destroy workflow requires you to type yes in the "Type 'yes' to confirm destruction" input when you run the workflow; otherwise the workflow will not proceed.

  1. Destroy Application:
    • Go to GitHub β†’ Actions tab
    • Select "04 - Application Destroying" workflow (04-application_destroying.yaml)
    • Click "Run workflow"
    • Type yes in the confirmation input
    • Select environment (prod or dev) and region
    • Click "Run workflow"

    What the workflow does:

    • Uses AWS_STATE_ACCOUNT_ROLE_ARN for backend state operations
    • Uses environment-specific deployment account role ARN
    • Uses AWS_ASSUME_EXTERNAL_ID for cross-account role assumption
    • Exports TERRAFORM_WORKSPACE and BACKEND_PREFIX for state path consistency
    • Creates backend.hcl from template using APPLICATION_PREFIX for state file key
    • Creates backend_infra/backend.hcl from template (needed for remote state access)
    • Creates application_infra/backend.hcl from template (needed for remote state access)
    • Retrieves password secrets (PostgreSQL, Redis, OpenLDAP admin, and optional admin-seed) from GitHub repository secrets
    • Sets Kubernetes environment variables using set-k8s-env.sh (from application_infra)
    • Runs Terraform destroy operations to remove PostgreSQL, Redis, SES, SNS, ArgoCD Applications, and Route53 record for 2FA app
  2. Destroy Application Infrastructure:
    • Go to GitHub β†’ Actions tab
    • Select "02 - Application Infra Destroying" workflow (02-application_infra_destroying.yaml)
    • Click "Run workflow"
    • Type yes in the confirmation input
    • Select environment (prod or dev) and region
    • Click "Run workflow"
  3. Destroy Backend Infrastructure:
    • Go to GitHub β†’ Actions tab
    • Select "01 - Backend Infra Destroying" workflow (01-backend_infra_destroying.yaml)
    • Click "Run workflow"
    • Type yes in the confirmation input
    • Select environment (prod or dev) and region
    • Click "Run workflow"
  4. Destroy State Backend (if needed):
    • Go to GitHub β†’ Actions tab
    • Select "00 - TF Backend State Destroying" workflow (00-tfstate_infra_destroying.yaml)
    • Click "Run workflow"
    • Type yes in the confirmation input
    • Click "Run workflow"

For detailed GitHub Actions setup: GitHub Repository Configuration | GitHub Actions Deployment

Deployment Comparison

Feature Local Deployment GitHub Actions
Setup Complexity Medium (requires local tools) Low (web-based)
Best For Development, testing Production, CI/CD
Requires GitHub CLI Yes No
Requires GitHub Secrets Optional (can use env vars) Required
Automation Level Script-based Fully automated

Deployment Order

The deployment follows a three-tier approach that must be executed in order. Each tier depends on the previous one:

  1. Deploy Terraform Backend State Infrastructure
    • Purpose: Creates S3 bucket for storing Terraform state files
    • Components: S3 bucket with versioning, encryption, and file-based locking
    • Account: State Account (Account A)
    • Local: cd tf_backend_state && ./set-state.sh (or ./tf_backend_state/set-state.sh from repo root; scripts change to their directory automatically)
    • GitHub Actions: "00 - TF Backend State Provisioning" (00-tfstate_infra_provisioning.yaml); defaults AWS_REGION to us-east-1 when not set
    • See: Terraform Backend State README
  2. Deploy Backend Infrastructure
    • Purpose: Creates foundational AWS infrastructure for Kubernetes workloads
    • Components: VPC with public/private subnets, EKS cluster (Auto Mode), VPC endpoints (SSM, STS, SNS), IRSA (OIDC provider), ECR repository
    • Account: Deployment Account (Account B)
    • Prerequisites: Terraform backend state must be deployed first
    • Local: cd backend_infra && ./setup-backend.sh
    • GitHub Actions: "01 - Backend Infra Provisioning" (01-backend_infra_provisioning.yaml)
    • See: Backend Infrastructure README
  3. Deploy Application Infrastructure
    • Purpose: Deploys LDAP stack, supporting services, ALB, Route53 records, and infrastructure for 2FA application on the EKS cluster
    • Components: OpenLDAP stack (HA), PhpLdapAdmin, LTB-passwd, ALB, Route53 records (for phpldapadmin and ltb-passwd), ArgoCD Capability (optional), StorageClass
    • Account: Deployment Account (Account B)
    • Prerequisites: Backend infrastructure (EKS cluster) must be deployed first
    • Additional Requirements: Route53 hosted zone (Account A) and ACM certificate (Account B) must exist
      • Route53 hosted zone in State Account; ACM certificate in Deployment Account (ALB and cert must be in same account)
      • Automatically accessed via state_account_role_arn for Route53 (injected by scripts/workflows)
      • See Cross-Account Access Documentation for configuration details
    • Local: cd application_infra && ./setup-application-infra.sh
    • GitHub Actions: "02 - Application Infra Provisioning" (02-application_infra_provisioning.yaml)
    • See: Application Infrastructure README | Application Deployment README
  4. Build Container Images
    • Purpose: Build and push backend and frontend container images to ECR
    • Required: Must be completed BEFORE deploying the application
    • Workflows: "03 - Backend Build and Push" (03-backend_build_push.yaml) and "03 - Frontend Build and Push" (03-frontend_build_push.yaml) (can run in parallel)
    • Note: These workflows update Helm values.yaml files with new image tags and commit changes to git
  5. Deploy Application
    • Purpose: Deploys the 2FA application components, including PostgreSQL, Redis, SES, SNS, ArgoCD Applications, and the 2FA application (backend/frontend)
    • Components: PostgreSQL (user registration data), Redis (SMS OTP and login challenge storage), AWS SES (email verification), AWS SNS (SMS 2FA), ArgoCD Applications (GitOps), Route53 record (for 2FA app), Backend application (Python FastAPI), Frontend application (HTML/JS/CSS + nginx)
    • Account: Deployment Account (Account B)
    • Prerequisites: Application infrastructure (Step 3) must be deployed first, including StorageClass, ArgoCD Capability, and ALB DNS name. Container images must be built and pushed to ECR before deployment (see Build Container Images step above).
    • Local: cd application && ./setup-application.sh
    • GitHub Actions: "04 - Application Provisioning" (04-application_provisioning.yaml)
    • See: Application Deployment README for complete deployment guide
    • Note: The 2FA application backend and frontend can be deployed via ArgoCD Applications (if enabled) or manually using Helm charts. See Deploying the 2FA Application for manual deployment instructions.

Destroy Order: Always destroy in reverse order: Application β†’ Application Infrastructure β†’ Backend Infrastructure β†’ State

For detailed deployment information: Deployment Methods | Deployment Overview

Architecture

LDAP 2FA unified architecture diagram

Multi-Account Architecture

This project uses a multi-account architecture for enhanced security. See the Prerequisites table above for a quick reference of what resides in each account.

  • Account A (State Account): Holds shared, centrally managed resources
    • Terraform state: S3 bucket with versioning, encryption, and file-based locking
    • AWS Secrets Manager: Secrets github-role, tf-vars, external-id (role ARNs, passwords, ExternalId for local scripts)
    • Route53 Hosted Zone: Domain and DNS; CNAME records for ACM validation are created here
    • GitHub Actions authenticates with Account A via OIDC for backend operations and Route53
    • IAM-based access control (no access keys required)
  • Account B (Deployment Account): Holds infrastructure and applications
    • EKS cluster, VPC, ALB, ECR, application workloads
    • ACM Certificates: Requested and stored here (ALB and certificate must be in the same account); validated via CNAME records in Account A's Route53
    • Separate roles for production and development environments (optional)
    • Terraform provider assumes Account B role via cross-account role assumption

For detailed architecture documentation: Multi-Account Architecture | Terraform Backend State README

How It Works

The multi-account architecture enables secure separation of concerns:

  1. GitHub Actions authenticates with Account A via OIDC (no access keys required) for Terraform backend access
  2. Terraform backend uses Account A credentials to read/write state files in S3
  3. Terraform AWS provider assumes Account B role (via assume_role with ExternalId) for resource deployment
  4. Remote state data sources use Account A credentials to read state from Account A, enabling cross-tier dependencies

This architecture ensures state files are isolated in a dedicated account while resource deployment uses separate credentials, providing enhanced security and better compliance capabilities.

Project Structure

ldap-2fa-on-k8s/
β”œβ”€β”€ docs/auxiliary/   # Auxiliary documentation (reference, application, application_infra, troubleshooting)
β”œβ”€β”€ tf_backend_state/      # Terraform state backend infrastructure (S3) - Account A
β”œβ”€β”€ backend_infra/         # Core AWS infrastructure (VPC, EKS, VPC endpoints, IRSA) - Account B
β”‚   └── monitor-deployments.sh   # Backend-infra deployment monitoring
β”œβ”€β”€ application_infra/      # Application infrastructure (OpenLDAP, ALB, ArgoCD Capability) - Account B
β”‚   β”œβ”€β”€ helm/              # Helm values for OpenLDAP stack
β”‚   β”œβ”€β”€ monitor-deployments.sh   # Application-infra deployment monitoring
β”‚   └── modules/           # Infrastructure Terraform modules (ALB, ArgoCD Capability, etc.)
β”œβ”€β”€ application/           # 2FA Application code and dependencies - Account B
β”‚   β”œβ”€β”€ backend/           # 2FA Backend (Python FastAPI)
β”‚   β”œβ”€β”€ frontend/          # 2FA Frontend (HTML/JS/CSS + nginx)
β”‚   β”œβ”€β”€ helm/              # Helm values for PostgreSQL and Redis
β”‚   β”œβ”€β”€ monitor-deployments.sh   # Application deployment monitoring
β”‚   └── modules/           # Application Terraform modules (PostgreSQL, Redis, SES, SNS, ArgoCD Apps)
β”œβ”€β”€ scripts/               # Shared scripts (run from repo root or workflows)
β”‚   β”œβ”€β”€ assume-github-role.sh   # AWS role assumption (State/Dev/Prod)
β”‚   β”œβ”€β”€ get-eks-token.sh        # EKS auth token for Terraform exec plugin
β”‚   β”œβ”€β”€ mirror-images-to-ecr.sh # Mirror Docker Hub images to ECR
β”‚   └── set-k8s-env.sh         # Kubernetes env and kubeconfig setup
└── .github/workflows/     # GitHub Actions workflows for CI/CD

Shared Scripts (scripts/)

Shared automation scripts live in scripts/. Workflows and setup/destroy scripts reference them as ../scripts/....

  • assume-github-role.sh: Assume AWS roles (State, Dev, Prod). Used by the ArgoCD module and for manual role switching.
  • get-eks-token.sh: Used by Terraform Kubernetes and Helm providers (exec plugin) to generate a fresh EKS token on every API call, avoiding token timeout during long operations (e.g. 20-minute Helm timeouts). Supports optional cross-account role assumption via ASSUME_ROLE_ARN and ASSUME_EXTERNAL_ID.
  • mirror-images-to-ecr.sh: Mirrors Docker Hub images (OpenLDAP, Redis, PostgreSQL, phpldapadmin, ltb-passwd) to ECR. Run by setup-application-infra.sh and the 02 - Application Infra Provisioning workflow (02-application_infra_provisioning.yaml).
  • set-k8s-env.sh: Sets Kubernetes environment variables and updates kubeconfig from backend_infra Terraform state. Sourced by application_infra and application setup/destroy scripts and workflows.

Backend Infrastructure Components

  • VPC with public and private subnets across multiple availability zones
  • EKS Cluster in Auto Mode with automatic node provisioning and CloudWatch logging
  • IRSA (IAM Roles for Service Accounts) for secure pod-to-AWS-service authentication via OIDC
  • VPC Endpoints for private AWS service access:
    • SSM endpoints for secure node access (Session Manager)
    • STS endpoint for IRSA (IAM role assumption) - enabled by default
    • SNS endpoint for SMS 2FA (optional, requires enable_sns_endpoint = true)
  • ECR Repository for container image storage with lifecycle policies
  • Terraform State Backend using S3 with file-based locking for state concurrency control (migrated from DynamoDB for simplicity and cost efficiency)

Application Infrastructure Components

  • OpenLDAP Stack HA deployed via vendored Helm chart (application_infra/charts/openldap-stack-ha, version 5.0.0, osixia/openldap:1.5.0; see OPENLDAP_CHANGELOG.md) with:
    • OpenLDAP StatefulSet (3 replicas for high availability)
    • PhpLdapAdmin web interface
    • LTB-passwd self-service password management
    • Credentials from Terraform-managed Kubernetes secret (keys LDAP_ADMIN_PASSWORD and LDAP_CONFIG_PASSWORD for osixia image)
    • ACM certificate configured in IngressClassParams (ALB module), not passed to OpenLDAP module
  • Application Load Balancer (ALB) via EKS Auto Mode:
    • Internet-facing ALB with HTTPS/TLS termination
    • Single ALB handles multiple Ingresses via host-based routing
    • Automatic provisioning via IngressClass and IngressClassParams
    • Certificate ARN and group name configured at cluster level
  • ArgoCD Capability (AWS EKS managed service) for GitOps infrastructure with AWS Identity Center
  • Network Policies for securing pod-to-pod communication with cross-namespace support
  • Route53 DNS records for phpldapadmin and ltb-passwd subdomains pointing to ALB
  • Persistent Storage using EBS-backed StorageClass (shared by all namespaces)

For detailed infrastructure documentation: Application Infrastructure README

Application Components

  • 2FA Application with LDAP authentication integration:
    • Python FastAPI backend with TOTP and SMS MFA support (Backend Documentation)
    • Static HTML/JS/CSS frontend with modern UI (Frontend Documentation)
    • Single domain routing (app.<domain>) with path-based access
    • Self-service user registration with email/phone verification
    • Admin dashboard for user management and group operations
    • Interactive API documentation (Swagger UI and ReDoc)
  • PostgreSQL (Bitnami Helm chart) for user registration and verification token storage
  • Redis (Bitnami Helm chart) for SMS OTP and login challenge storage with TTL-based expiration (required)
  • AWS SES integration for email verification and notifications (IRSA-based)
  • AWS SNS integration for SMS-based 2FA verification (optional, requires VPC endpoint)
  • ArgoCD Applications for GitOps-driven backend and frontend deployments
  • Route53 DNS record for 2FA application subdomain (app.<domain>)

For detailed application documentation: Application Deployment README

Note: Application components require infrastructure to be deployed first. See Application Infrastructure README for infrastructure deployment.

Documentation

Infrastructure Documentation

Terraform Backend State

S3 state management and GitHub variable configuration

Backend Infrastructure

VPC, EKS, IRSA, VPC endpoints, and ECR documentation

Application Infrastructure Application Deployment

OpenLDAP, 2FA app, ALB, ArgoCD, and deployment instructions

Application Documentation

Backend API Documentation

Comprehensive FastAPI backend documentation: architecture, installation, configuration, API endpoints, deployment, and security

Frontend Application Documentation

Complete frontend SPA documentation: architecture, features, deployment, development, security, and troubleshooting

2FA Application PRD

Product requirements for the 2FA application (API specs, frontend architecture, Swagger UI)

User Signup Management PRD

Self-service user registration with email/phone verification and profile state management

Admin Functions PRD

Admin dashboard, group CRUD operations, user management, and approval workflows

SMS OTP Management PRD

Redis-based SMS OTP and login challenge storage with TTL-based automatic expiration (required)

OpenLDAP README

OpenLDAP configuration and TLS setup

Osixia OpenLDAP Requirements

OpenLDAP image requirements and configuration details

Deploying the 2FA Application

Complete step-by-step guide for deploying backend and frontend 2FA applications

Secret Dependencies

Which components require which secrets (PostgreSQL, Redis, LDAP admin)

Password and MFA Flow

Password and MFA flow documentation

Redis Enablement Summary

Redis (required) for SMS OTP and login challenges; verification steps

Backend Schema Reference

PostgreSQL, OpenLDAP, and Redis schema (users, groups, verification_tokens, Redis keys)

Secrets Requirements

Complete guide for managing secrets via GitHub and AWS Secrets Manager

Module Documentation

ALB Module

EKS Auto Mode ALB configuration

ArgoCD Capability Module

AWS managed ArgoCD setup

ArgoCD Application Module

GitOps application deployment

cert-manager Module

TLS certificate management (module exists, not currently used)

Network Policies Module

Pod-to-pod security

OpenLDAP Module

LDAP directory service deployment

PostgreSQL Module

User data and verification token storage

Redis Module

SMS OTP and login challenge storage (required)

SES Module

Email verification and notifications

SNS Module

SMS 2FA integration

Route53 Module

Route53 hosted zone management

Route53 Record Module

Route53 A (alias) records for ALB

VPC Endpoints Module

Private AWS service access

ECR Module

Container registry setup

EBS Module

EBS storage configuration

Configuration Documentation

ALB Configuration PRD

Application Load Balancer configuration details

ArgoCD Capability Configuration PRD

GitOps deployment configuration

Domain Configuration PRD

Route53 and domain setup

OpenLDAP Deployment PRD

OpenLDAP deployment requirements and configuration

Cross-Account Access Documentation

Route53 and ACM certificate cross-account access configuration

Auxiliary Documentation

Design docs, guides, and troubleshooting under docs/auxiliary/:

  • Reference: Secrets Requirements, Debug Commands (copy-paste commands for OpenLDAP, ArgoCD, admin-seed, 2FA backend, EKS)
  • Application: design (PRDs), deployment, guides (password flow, Redis, secret dependencies), schema (PostgreSQL, OpenLDAP, Redis)
  • Application infrastructure: design (PRDs), guides (cross-account, OpenLDAP, security)
  • Troubleshooting: Troubleshooting Index, Frontend Troubleshooting

Security & Operations

Security Improvements

Security enhancements and best practices

Project Changelog

All project changes including latest features and improvements

Backend Infrastructure Changelog

Backend infrastructure changes (VPC, EKS, IRSA, VPC endpoints)

Application Infrastructure Changelog

OpenLDAP, ALB, ArgoCD Capability, and infrastructure changes

Application Changelog

2FA app, PostgreSQL, Redis, SES, SNS, and ArgoCD Applications changes

Terraform Backend State Changelog

S3 state management changes (v1.0.0 with file-based locking)

Accessing the Services

After deployment, the following services are available:

2FA Application

URL: https://app.<your-domain> (e.g., https://app.example.com)

The full-stack 2FA application provides:

  • Sign Up Flow:
    • User signs up β†’ status set to PENDING (email and phone verification sent)
    • User verifies email β†’ email_verified = true
    • User verifies phone β†’ phone_verified = true
    • When both verified β†’ status automatically changes to COMPLETE
    • Admin activates user and assigns group(s) (required) β†’ status changes to ACTIVE
    Note: MFA method selection does not occur during signup. Users cannot log in while in PENDING or COMPLETE states.
  • Login Flow (ACTIVE users only):
    • Step 1 – username and password only (with optional "Remember me" for longer-lived session and "Forgot your password?" link)
    • Step 2 – MFA screen where the user chooses Authenticator app or SMS and enters the 6-digit code (from the app or sent via SMS)
    • Dual MFA methods (TOTP and SMS) are supported
    • Users enroll in their preferred MFA method during login
    • Once logged in, users can execute actions based on their assigned group(s) (e.g., Admin group members see admin dashboard, User group members have standard access)
  • Forgot/Reset password: User requests a reset link by email; opening the link shows a set-new-password form; after submitting, they are redirected to login and sign in with the new password.
  • Authenticator app: one-time setup with QR code (or manual secret); on subsequent logins the user just enters the code. User can choose Authenticator or SMS on every login.
  • SMS: user requests a code on the MFA step; 6-digit OTP is sent via AWS SNS; user enters it in the same verification code field.
  • User profile management with email change (verification link), phone change (SMS code), and password change
  • Admin dashboard for user management, group CRUD operations, and approval workflows (visible to LDAP admin group members only)
  • First admin (optional): You can seed the first admin so they use the same username/password as the LDAP admin; set ADMIN_SEED_* secrets (see Secrets Requirements). The seeded admin does not have MFA pre-configured; they follow the same two-step login flow and set up Authenticator or use SMS like any other user.
  • Interactive API Documentation (always enabled):
    • /api/docs - Swagger UI for interactive API exploration and testing
    • /api/redoc - ReDoc alternative API documentation interface
    • /api/openapi.json - OpenAPI schema in JSON format

Documentation: Backend API Documentation | Frontend Application Documentation

PhpLdapAdmin

URL: https://phpldapadmin.<your-domain> (e.g., https://phpldapadmin.example.com)

  • Web-based LDAP administration interface for managing directory entries
  • Internet-facing access via Application Load Balancer with HTTPS/TLS termination
  • Requires OpenLDAP admin credentials for authentication

LTB-passwd

URL: https://passwd.<your-domain> (e.g., https://passwd.example.com)

  • Self-service password management UI for LDAP users
  • Allows users to reset their LDAP passwords without administrator intervention
  • Internet-facing access via Application Load Balancer with HTTPS/TLS termination

ArgoCD

URL: Retrieved from Terraform output argocd_server_url (if ArgoCD is enabled)

  • AWS EKS managed ArgoCD service for GitOps deployments
  • Declarative, Git-driven application deployments
  • AWS Identity Center (SSO) authentication for secure access
  • Automatic synchronization and self-healing capabilities

LDAP Service

Access: Cluster-internal only (ClusterIP service)

  • Port: 389 (LDAP), 636 (LDAPS)
  • Not Exposed: LDAP ports are not accessible outside the cluster

MFA Methods

Method Description Infrastructure Required
TOTP Time-based One-Time Password using authenticator apps (Google Authenticator, Authy, etc.) None (codes generated locally)
SMS Verification codes sent via AWS SNS to user's phone SNS VPC endpoint, IRSA role

Security Considerations

Key Security Features

This project implements defense-in-depth security across multiple layers:

  • Secrets Management: Passwords managed via GitHub repository secrets (CI/CD) or AWS Secrets Manager (local) with automated retrievalβ€”never committed to version control
  • IRSA (IAM Roles for Service Accounts): Pods assume IAM roles via OIDCβ€”no long-lived AWS credentials stored in containers or environment variables
  • VPC Endpoints: AWS service access (SSM, STS, SNS) routed through private endpointsβ€”no public internet exposure for sensitive operations
  • TLS/HTTPS: TLS termination at ALB using ACM certificates; OpenLDAP uses auto-generated self-signed certificates (cert-manager module available but not used)
  • LDAP Security: ClusterIP service only (not exposed externally); cross-namespace access restricted to secure ports (443, 636, 8443)
  • Network Policies: Kubernetes Network Policies restrict pod-to-pod communication to encrypted ports with cross-namespace support for authorized services
  • Storage Encryption: EBS volumes encrypted by default; S3 state files encrypted with AES256 server-side encryption
  • Network Isolation: EKS nodes deployed in private subnets with no public IPs; access via SSM Session Manager
  • Multi-Account Architecture: State storage (Account A) isolated from resource deployment (Account B) for enhanced security and compliance
  • State Locking: S3 file-based locking prevents concurrent Terraform operations and state corruption
  • OIDC Authentication: GitHub Actions authenticates with AWS via OIDCβ€”no access keys required
  • Cross-Account Security: ExternalId required for cross-account role assumption to prevent confused deputy attacks; bidirectional trust relationships
  • Helm Release Safety: Comprehensive Helm release attributes (atomic, force_update, replace, cleanup_on_fail, recreate_pods, wait, wait_for_jobs, upgrade_install) for safer deployments
  • ECR Image Support: All modules (OpenLDAP, PostgreSQL, Redis) use ECR images instead of Docker Hub to prevent rate limiting
  • Kubeconfig Auto-Update: Automatic kubeconfig updates prevent stale cluster endpoints and DNS lookup errors

For detailed security documentation: Security Improvements

Support

Operations & Monitoring

Deployment Monitoring Scripts

There are three layer-specific monitoring scripts. Each prompts for region and environment, retrieves credentials from AWS Secrets Manager, updates kubeconfig, and produces a report suitable for agent investigation or manual review.

  • backend_infra/monitor-deployments.sh - Backend infrastructure (EKS, VPC, ECR, etc.)
  • application_infra/monitor-deployments.sh - Application infrastructure (ArgoCD, OpenLDAP, ALB, ingress)
  • application/monitor-deployments.sh - Application layer (PostgreSQL, Redis, 2FA app, ingress, ALB)

Each script provides interactive setup, cluster access, layer-specific health checks, and color-coded output. Exit code 0 = healthy, 1 = issues found. Prerequisites: jq, kubectl, helm, AWS CLI; optional: GitHub CLI (gh).

Usage
cd backend_infra && ./monitor-deployments.sh
cd application_infra && ./monitor-deployments.sh
cd application && ./monitor-deployments.sh

Role Assumption Script (scripts/assume-github-role.sh)

The role assumption script is located in scripts/assume-github-role.sh. It allows you to switch between AWS account roles (State, Dev, Prod) when working in the terminal.

Features
  • Account Role Switching: Switch between State Account, Development Account, or Production Account roles
  • Automatic Configuration: Retrieves role ARNs from AWS Secrets Manager (secret: github-role)
  • Credential Persistence: Credentials persist in your current shell when script is sourced
  • Clean Option: Remove all AWS credentials with clean option
  • Interactive Mode: Prompts for account selection if no argument is provided
Usage
# Source the script (recommended - credentials persist in current shell)
source ./scripts/assume-github-role.sh [state|dev|prod|clean]

# Or use eval
eval $(./scripts/assume-github-role.sh [state|dev|prod|clean])

# Interactive mode (prompts for account selection)
source ./scripts/assume-github-role.sh
Options
  • state - Assume State Account role (for backend state operations)
  • dev - Assume Development Account role
  • prod - Assume Production Account role
  • clean - Remove all AWS credentials from environment
  • No argument - Interactive prompt for account selection

Note: The script must be sourced for credentials to persist. Use source ./scripts/assume-github-role.sh [option] or eval $(./scripts/assume-github-role.sh [option]).

For detailed documentation: See Operations & Monitoring in the main README.

Troubleshooting

Troubleshooting Index – Central index (deployment, application layer, LDAP admin seed, secrets, debug commands).

Copy-paste commands: Debug Commands. ArgoCD capability IAM: see Debug Commands (ArgoCD section) and the ArgoCD module README.

Repository

GitHub Repository: ldap-2fa-on-k8s

License

This project is licensed under the MIT License.

See the LICENSE file for details.

Copyright (c) 2025 Tal Orlik