Chuyển đến nội dung chính

Bài 16: Workflows - Tự động hóa quản trị với IGA

Giới thiệu Keycloak Workflows (preview) cho Identity Governance and Administration (IGA). Understanding workflows, workflow definitions, workflow expression language, managing workflows, defining conditions và steps, Joiner-Mover-Leaver (JML) processes, automated onboarding/offboarding, access reviews và common use cases cho enterprise.

🔒 DevSecOps — Bài 16 Bài 16: Workflows - Tự động hóa quản trị với IGA

Keycloak từ Cơ bản đến Nâng cao

Phần 4: User Federation, Organizations và Authorization

xdev.asia

1. IGA và Workflows — Tổng quan

Identity Governance and Administration (IGA) là lĩnh vực quản trị vòng đời identity — từ khi user gia nhập tổ chức (joiner), thay đổi vai trò (mover), đến khi rời đi (leaver). Keycloak Workflows là tính năng preview cho phép tự động hóa các quy trình IGA này.

1.1 Tại sao cần Workflows?

Vấn đềKhông có WorkflowsCó Workflows
Onboarding nhân viên mớiAdmin phải manually tạo account, gán roles, thêm vào groupsTự động: tạo account → gán roles theo department → thêm groups → gửi welcome email
Chuyển phòng banAdmin phải manually xóa roles cũ, gán roles mớiTự động detect thay đổi department → update roles/groups tương ứng
OffboardingQuên revoke access → security riskTự động disable account → revoke sessions → remove from groups
Access reviewKhông thể review định kỳTự động kiểm tra và báo cáo unused permissions

1.2 Keycloak Workflows là gì?

Keycloak Workflows là hệ thống event-driven, condition-based cho phép định nghĩa các quy trình tự động gồm:

  • Trigger: Sự kiện kích hoạt workflow (user created, attribute changed, login event...)
  • Conditions: Điều kiện phải thỏa mãn để workflow thực thi
  • Steps: Các hành động thực hiện khi conditions matched (assign role, add to group, send notification...)

2. Bật Workflows (Preview Feature)

Workflows là tính năng preview, cần bật explicitly:

# Bật qua command line
bin/kc.sh start --features=workflows

# Hoặc trong keycloak.conf
features=workflows

# Bật cùng các features khác
bin/kc.sh start --features=workflows,organizations,scripts

# Docker
docker run -e KC_FEATURES=workflows \
  quay.io/keycloak/keycloak:latest start-dev

Lưu ý: Preview features có thể thay đổi API giữa các phiên bản. Không nên dùng trong production mà không test kỹ.

3. Workflow Concepts

3.1 Workflow Components

Workflow Definition
├── Metadata (name, description, enabled)
├── Trigger (event type)
├── Conditions (when to execute)
│   ├── Condition 1 (attribute check)
│   ├── Condition 2 (group membership)
│   └── ... (AND/OR logic)
└── Steps (what to do)
    ├── Step 1 (assign role)
    ├── Step 2 (add to group)
    ├── Step 3 (send notification)
    └── ... (sequential execution)

3.2 Trigger Types

TriggerMô tảUse case
USER_CREATEDUser mới được tạoOnboarding: auto-assign roles/groups
USER_UPDATEDUser attributes thay đổiMover: detect department change
USER_DELETEDUser bị xóaCleanup: revoke external access
USER_DISABLEDUser bị disableOffboarding: revoke sessions
GROUP_MEMBERSHIP_CHANGEDUser thêm/xóa khỏi groupAuto-assign related roles
ROLE_ASSIGNEDUser được gán roleCascading permissions
ROLE_REMOVEDUser bị xóa roleRevoke dependent access

4. Workflow Definitions

4.1 Cấu trúc Workflow Definition

{
  "name": "Employee Onboarding",
  "description": "Automatically provision new employees based on department",
  "enabled": true,
  "trigger": {
    "type": "USER_CREATED"
  },
  "conditions": [
    {
      "type": "user_attribute",
      "attribute": "employeeType",
      "operator": "equals",
      "value": "full-time"
    }
  ],
  "steps": [
    {
      "type": "assign_role",
      "role": "employee"
    },
    {
      "type": "add_to_group",
      "group": "/Company/All-Employees"
    },
    {
      "type": "conditional",
      "condition": {
        "type": "user_attribute",
        "attribute": "department",
        "operator": "equals",
        "value": "engineering"
      },
      "thenSteps": [
        {
          "type": "assign_role",
          "role": "developer"
        },
        {
          "type": "add_to_group",
          "group": "/Company/Engineering"
        }
      ]
    }
  ]
}

4.2 YAML Format

# workflow-onboarding.yaml
name: Employee Onboarding
description: Auto-provision new employees
enabled: true

trigger:
  type: USER_CREATED

conditions:
  - type: user_attribute
    attribute: employeeType
    operator: equals
    value: full-time

steps:
  - type: assign_role
    role: employee

  - type: add_to_group
    group: /Company/All-Employees

  - type: conditional
    condition:
      type: user_attribute
      attribute: department
      operator: equals
      value: engineering
    thenSteps:
      - type: assign_role
        role: developer
      - type: add_to_group
        group: /Company/Engineering

  - type: conditional
    condition:
      type: user_attribute
      attribute: department
      operator: equals
      value: marketing
    thenSteps:
      - type: assign_role
        role: marketer
      - type: add_to_group
        group: /Company/Marketing

5. Workflow Expression Language

Workflows sử dụng expression language để định nghĩa conditions động và tham chiếu user attributes.

5.1 Variables

VariableMô tảVí dụ
user.usernameUsername của userjohn.doe
user.emailEmail address[email protected]
user.firstNameFirst nameJohn
user.lastNameLast nameDoe
user.attributes.{name}Custom attributeuser.attributes.department
user.groupsList group paths["/Engineering", "/VPN-Users"]
user.rolesList assigned roles["employee", "developer"]
event.typeEvent typeUSER_CREATED
event.timeEvent timestamp2026-03-30T10:00:00Z

5.2 Operators

OperatorMô tảVí dụ
equalsSo sánh bằnguser.attributes.department equals "engineering"
not_equalsKhông bằnguser.attributes.status not_equals "inactive"
containsChứa giá trịuser.email contains "@acme.com"
starts_withBắt đầu bằnguser.username starts_with "svc-"
ends_withKết thúc bằnguser.email ends_with "@acme.com"
inThuộc danh sáchuser.attributes.location in ["HCM", "HN", "DN"]
existsAttribute tồn tạiuser.attributes.employeeId exists
not_existsAttribute không tồn tạiuser.attributes.termination_date not_exists

5.3 Functions

# String functions
upper(user.attributes.department)        → "ENGINEERING"
lower(user.email)                        → "[email protected]"
trim(user.attributes.title)              → "Senior Developer"

# Date functions
now()                                    → current timestamp
daysAgo(30)                              → timestamp 30 ngày trước
daysBetween(user.createdTimestamp, now()) → số ngày từ lúc tạo

# Collection functions
size(user.groups)                        → số groups
contains(user.roles, "admin")            → true/false

6. Managing Workflows

6.1 CRUD Operations

# Tạo workflow
curl -X POST "http://localhost:8080/admin/realms/my-realm/workflows" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Employee Onboarding",
    "description": "Auto-provision new employees",
    "enabled": true,
    "trigger": { "type": "USER_CREATED" },
    "conditions": [
      {
        "type": "user_attribute",
        "attribute": "employeeType",
        "operator": "equals",
        "value": "full-time"
      }
    ],
    "steps": [
      { "type": "assign_role", "role": "employee" },
      { "type": "add_to_group", "group": "/All-Employees" }
    ]
  }'

# List workflows
curl -s "http://localhost:8080/admin/realms/my-realm/workflows" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'

# Get workflow by ID
WORKFLOW_ID="workflow-uuid"
curl -s "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'

# Update workflow
curl -X PUT "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Employee Onboarding v2",
    "enabled": true,
    "steps": [
      { "type": "assign_role", "role": "employee" },
      { "type": "add_to_group", "group": "/All-Employees" },
      { "type": "invoke_api", "url": "https://hr.example.com/api/provisioned", "method": "POST" }
    ]
  }'

# Delete workflow
curl -X DELETE "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

# Enable/Disable workflow
curl -X PUT "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'

7. Defining Conditions

7.1 User Attribute Conditions

{
  "conditions": [
    {
      "type": "user_attribute",
      "attribute": "department",
      "operator": "equals",
      "value": "engineering"
    },
    {
      "type": "user_attribute",
      "attribute": "employeeType",
      "operator": "in",
      "values": ["full-time", "contract"]
    }
  ]
}

7.2 Group Membership Conditions

{
  "conditions": [
    {
      "type": "group_membership",
      "group": "/Engineering",
      "operator": "is_member"
    }
  ]
}

7.3 Time-based Conditions

{
  "conditions": [
    {
      "type": "time",
      "attribute": "user.createdTimestamp",
      "operator": "older_than_days",
      "value": 90
    }
  ]
}

7.4 Event-based Conditions

{
  "conditions": [
    {
      "type": "event",
      "attribute": "event.details.updated_attribute",
      "operator": "equals",
      "value": "department"
    }
  ]
}

8. Defining Steps

8.1 Assign Role

{
  "type": "assign_role",
  "role": "employee",
  "scope": "realm"
}

// Client role
{
  "type": "assign_role",
  "role": "editor",
  "scope": "client",
  "clientId": "my-app"
}

8.2 Remove Role

{
  "type": "remove_role",
  "role": "contractor",
  "scope": "realm"
}

8.3 Add to Group

{
  "type": "add_to_group",
  "group": "/Company/Engineering/Backend"
}

8.4 Remove from Group

{
  "type": "remove_from_group",
  "group": "/Company/Marketing"
}

8.5 Send Notification

{
  "type": "send_email",
  "to": "${user.email}",
  "template": "welcome-employee",
  "params": {
    "name": "${user.firstName}",
    "department": "${user.attributes.department}",
    "manager": "${user.attributes.manager}"
  }
}

8.6 Invoke External API

{
  "type": "invoke_api",
  "url": "https://hr-system.example.com/api/v1/employees/provisioned",
  "method": "POST",
  "headers": {
    "Content-Type": "application/json",
    "X-API-Key": "${env.HR_API_KEY}"
  },
  "body": {
    "employeeId": "${user.attributes.employeeId}",
    "email": "${user.email}",
    "department": "${user.attributes.department}",
    "provisionedAt": "${event.time}"
  }
}

8.7 Set User Attribute

{
  "type": "set_attribute",
  "attribute": "provisionedAt",
  "value": "${now()}"
}

{
  "type": "set_attribute",
  "attribute": "accessLevel",
  "value": "standard"
}

9. Joiner-Mover-Leaver (JML) Processes

9.1 Joiner — Automated Onboarding

# workflow-joiner.yaml
name: "JML: Joiner - Employee Onboarding"
description: Auto-provision new full-time employees
enabled: true

trigger:
  type: USER_CREATED

conditions:
  - type: user_attribute
    attribute: employeeType
    operator: equals
    value: full-time

steps:
  # Base provisioning cho tất cả employees
  - type: assign_role
    role: employee

  - type: add_to_group
    group: /Company/All-Employees

  - type: set_attribute
    attribute: onboardingStatus
    value: completed

  - type: set_attribute
    attribute: onboardedAt
    value: "${now()}"

  # Department-specific provisioning
  - type: conditional
    condition:
      type: user_attribute
      attribute: department
      operator: equals
      value: engineering
    thenSteps:
      - type: assign_role
        role: developer
      - type: add_to_group
        group: /Company/Engineering
      - type: assign_role
        role: gitlab-user
        scope: client
        clientId: gitlab

  - type: conditional
    condition:
      type: user_attribute
      attribute: department
      operator: equals
      value: sales
    thenSteps:
      - type: assign_role
        role: sales-rep
      - type: add_to_group
        group: /Company/Sales
      - type: assign_role
        role: crm-user
        scope: client
        clientId: salesforce

  # Notify HR system
  - type: invoke_api
    url: https://hr.example.com/api/onboarding/completed
    method: POST
    body:
      employeeId: "${user.attributes.employeeId}"
      email: "${user.email}"

  # Send welcome email
  - type: send_email
    to: "${user.email}"
    template: welcome-employee

9.2 Mover — Department Transfer

# workflow-mover.yaml
name: "JML: Mover - Department Transfer"
description: Handle department changes
enabled: true

trigger:
  type: USER_UPDATED

conditions:
  - type: event
    attribute: event.details.updated_attribute
    operator: equals
    value: department

steps:
  # Lấy old department từ event details
  # Remove old department group
  - type: remove_from_group
    group: "/Company/${event.details.previous_value}"

  # Add to new department group
  - type: add_to_group
    group: "/Company/${user.attributes.department}"

  # Log transfer
  - type: set_attribute
    attribute: lastTransferDate
    value: "${now()}"

  - type: set_attribute
    attribute: previousDepartment
    value: "${event.details.previous_value}"

  # Notify managers
  - type: invoke_api
    url: https://hr.example.com/api/transfers
    method: POST
    body:
      employeeId: "${user.attributes.employeeId}"
      fromDepartment: "${event.details.previous_value}"
      toDepartment: "${user.attributes.department}"
      transferDate: "${event.time}"

9.3 Leaver — Automated Offboarding

# workflow-leaver.yaml
name: "JML: Leaver - Employee Offboarding"
description: Auto-deprovision disabled/deleted employees
enabled: true

trigger:
  type: USER_DISABLED

conditions:
  - type: user_attribute
    attribute: employeeType
    operator: in
    values:
      - full-time
      - contract

steps:
  # Revoke all active sessions
  - type: invoke_api
    url: "http://localhost:8080/admin/realms/my-realm/users/${user.id}/logout"
    method: POST
    headers:
      Authorization: "Bearer ${admin.token}"

  # Remove from all groups (except audit trail groups)
  - type: remove_from_group
    group: /Company/All-Employees

  # Remove sensitive roles
  - type: remove_role
    role: developer

  - type: remove_role
    role: admin

  # Set offboarding metadata
  - type: set_attribute
    attribute: offboardingStatus
    value: completed

  - type: set_attribute
    attribute: offboardedAt
    value: "${now()}"

  - type: set_attribute
    attribute: accountDisabledReason
    value: employee-departure

  # Notify IT and HR
  - type: invoke_api
    url: https://hr.example.com/api/offboarding/completed
    method: POST
    body:
      employeeId: "${user.attributes.employeeId}"
      email: "${user.email}"
      offboardedAt: "${event.time}"

  # Notify admin via email
  - type: send_email
    to: [email protected]
    template: offboarding-notification
    params:
      employeeName: "${user.firstName} ${user.lastName}"
      department: "${user.attributes.department}"

10. Access Review Workflows

Access reviews cho phép kiểm tra định kỳ xem users có còn cần các quyền hiện tại không:

# workflow-access-review.yaml
name: "Access Review: Inactive Users"
description: Review and flag inactive users
enabled: true

trigger:
  type: SCHEDULED
  schedule: "0 0 1 * *"  # Chạy hàng tháng

conditions:
  - type: user_attribute
    attribute: lastLoginTimestamp
    operator: older_than_days
    value: 90

steps:
  # Flag user for review
  - type: set_attribute
    attribute: accessReviewStatus
    value: pending-review

  - type: set_attribute
    attribute: accessReviewDate
    value: "${now()}"

  # Add to review group
  - type: add_to_group
    group: /Access-Review/Pending

  # Notify user's manager
  - type: send_email
    to: "${user.attributes.managerEmail}"
    template: access-review-notification
    params:
      employeeName: "${user.firstName} ${user.lastName}"
      lastLogin: "${user.attributes.lastLoginTimestamp}"
      reviewDeadline: "${daysFromNow(14)}"

  # If user has sensitive roles, escalate
  - type: conditional
    condition:
      type: role_assigned
      role: admin
    thenSteps:
      - type: send_email
        to: [email protected]
        template: access-review-escalation
        params:
          employeeName: "${user.firstName} ${user.lastName}"
          sensitiveRoles: "${user.roles}"

11. Common Enterprise Use Cases

11.1 Contractor Lifecycle Management

# Tự động disable contractor khi hết hợp đồng
name: "Contractor: Auto-disable on contract end"
enabled: true
trigger:
  type: SCHEDULED
  schedule: "0 0 * * *"  # Daily check

conditions:
  - type: user_attribute
    attribute: employeeType
    operator: equals
    value: contract
  - type: user_attribute
    attribute: contractEndDate
    operator: before
    value: "${now()}"

steps:
  - type: disable_user
  - type: set_attribute
    attribute: disabledReason
    value: contract-expired
  - type: send_email
    to: "${user.attributes.managerEmail}"
    template: contractor-expired

11.2 Auto-provisioning from LDAP Sync

# Khi user được sync từ LDAP, auto-assign roles
name: "LDAP: Post-sync provisioning"
enabled: true
trigger:
  type: USER_CREATED

conditions:
  - type: user_attribute
    attribute: LDAP_ID
    operator: exists
  - type: user_attribute
    attribute: department
    operator: exists

steps:
  - type: assign_role
    role: ldap-user
  - type: conditional
    condition:
      type: user_attribute
      attribute: memberOf
      operator: contains
      value: "CN=VPN-Users"
    thenSteps:
      - type: assign_role
        role: vpn-access

11.3 Compliance — Password Rotation Reminder

# Nhắc nhở user đổi password sau 90 ngày
name: "Compliance: Password rotation reminder"
enabled: true
trigger:
  type: SCHEDULED
  schedule: "0 8 * * 1"  # Weekly, Monday 8 AM

conditions:
  - type: user_attribute
    attribute: lastPasswordChange
    operator: older_than_days
    value: 80

steps:
  - type: send_email
    to: "${user.email}"
    template: password-rotation-reminder
    params:
      daysRemaining: "${90 - daysBetween(user.attributes.lastPasswordChange, now())}"
  - type: conditional
    condition:
      type: user_attribute
      attribute: lastPasswordChange
      operator: older_than_days
      value: 90
    thenSteps:
      - type: set_attribute
        attribute: passwordExpired
        value: "true"
      - type: invoke_api
        url: "http://localhost:8080/admin/realms/my-realm/users/${user.id}/execute-actions-email"
        method: PUT
        body: ["UPDATE_PASSWORD"]

12. Monitoring và Troubleshooting

12.1 Workflow Execution Logs

# Bật debug logging cho workflows
bin/kc.sh start \
  --features=workflows \
  --log-level=org.keycloak.workflow:DEBUG

# Xem workflow execution history
curl -s "http://localhost:8080/admin/realms/my-realm/workflows/${WORKFLOW_ID}/executions" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.'

# Response mẫu:
{
  "executions": [
    {
      "id": "exec-uuid-1",
      "workflowId": "workflow-uuid",
      "userId": "user-uuid",
      "status": "COMPLETED",
      "triggeredAt": "2026-03-30T10:00:00Z",
      "completedAt": "2026-03-30T10:00:02Z",
      "steps": [
        { "type": "assign_role", "status": "SUCCESS" },
        { "type": "add_to_group", "status": "SUCCESS" },
        { "type": "send_email", "status": "SUCCESS" }
      ]
    }
  ]
}

12.2 Common Issues

IssueNguyên nhânGiải pháp
Workflow không triggerFeature chưa bật hoặc workflow disabledKiểm tra --features=workflows và enabled: true
Condition không matchAttribute name hoặc value saiVerify user attributes qua Admin Console
Step failedRole/Group không tồn tạiTạo role/group trước khi reference trong workflow
API call failedExternal service không availableKiểm tra network connectivity, add retry logic
Email không gửiSMTP chưa cấu hìnhCấu hình Realm Settings → Email

13. Best Practices

  • Bắt đầu đơn giản — implement JML cơ bản trước, sau đó thêm complexity
  • Test workflows kỹ trước production — đây là preview feature, có thể có bugs
  • Idempotent steps — đảm bảo steps có thể chạy lại mà không gây side effects
  • Error handling — plan cho trường hợp external API fail hoặc email bounce
  • Audit trail — luôn set attributes ghi lại workflow execution (timestamps, status)
  • Separation of concerns — tách JML thành workflows riêng biệt (joiner, mover, leaver)
  • Version control — export workflow definitions vào git, dùng CI/CD để deploy
  • Monitor execution logs — set up alerting cho failed workflow executions
  • Gradual rollout — bật workflows cho nhóm nhỏ users trước, sau đó mở rộng