身分治理生命週期流程建立指南
使用時機
- 組織缺乏自動化的入職-調職-離職 (JML) 身分管理流程
- 存取權限佈建仍以手動方式進行,耗時數天,造成生產力損失與安全漏洞
- 前員工在離職後仍保留系統存取權 (孤兒帳戶)
- 角色數量爆炸,產生數千個擁有權不明且權限重疊的角色
- 合規要求強制執行文件化的身分生命週期流程 (SOX、HIPAA、GDPR)
- 缺乏對企業內部誰擁有什麼存取權限的集中可視性
請勿 用於單一應用程式的使用者管理;身分治理旨在處理需要將權威人資來源與下游應用程式佈建進行關聯的跨系統生命週期管理。
先決條件
- 權威人資系統 (Workday、SAP SuccessFactors、BambooHR) 作為身分識別來源
- IGA 平台 (SailPoint、Saviynt、One Identity) 或 Microsoft Entra ID Governance
- 作為主要目錄服務的 Active Directory 和/或 Azure AD
- 針對需要自動化佈建之目標系統的應用程式連接器
- 已定義的組織角色結構與呈報階層
- 來自人資、IT、資安及業務單位管理層的利害關係人支持
工作流程
步驟 1:定義身分生命週期狀態與轉換
繪製從入職到離職的身分生命週期地圖:
"""
身分生命週期狀態機
定義所有身分狀態及有效的轉換,並包含自動化動作。
"""
IDENTITY_LIFECYCLE = {
"states": {
"PRE_HIRE": {
"description": "開始日期前,根據人資資訊建立的身分記錄",
"automated_actions": [
"在 IGA 平台建立身分記錄",
"產生唯一的員工編號",
"預留信箱",
"根據職務代碼指派出生權角色",
"啟動背景調查流程"
],
"valid_transitions": ["ACTIVE", "CANCELLED"]
},
"ACTIVE": {
"description": "員工已入職,完整存取權限已佈建",
"automated_actions": [
"建立 Active Directory 帳戶",
"建立電子郵件信箱",
"佈建出生權的應用程式存取權",
"指派部門特定角色",
"加入通訊群組",
"發放 MFA Token/安全金鑰",
"若為遠端工作者則建立 VPN 帳戶"
],
"valid_transitions": ["ROLE_CHANGE", "LEAVE_OF_ABSENCE", "TERMINATED"]
},
"ROLE_CHANGE": {
"description": "員工已轉調、升遷或更換部門",
"automated_actions": [
"根據新職務代碼重新計算角色指派",
"移除前部門應用程式的存取權",
"佈建新部門應用程式的存取權",
"更新群組成員資格",
"在目錄中轉移主管",
"觸發保留權限的存取審查",
"通知新主管繼承的存取權"
],
"valid_transitions": ["ACTIVE", "LEAVE_OF_ABSENCE", "TERMINATED"]
},
"LEAVE_OF_ABSENCE": {
"description": "員工正在長期休假 (醫療、育嬰、留職停薪)",
"automated_actions": [
"停用互動式登入 (保留帳戶)",
"暫停 VPN 存取",
"設定自動回覆",
"將信箱委派給主管",
"保留所有角色指派以備復職",
"根據人資資料設定復職日期"
],
"valid_transitions": ["ACTIVE", "TERMINATED"]
},
"TERMINATED": {
"description": "員工已離職",
"automated_actions": [
"立即停用 AD 帳戶",
"撤銷所有應用程式存取權",
"撤銷 VPN 與遠端存取權",
"將信箱轉換為共用信箱 (主管可存取 90 天)",
"將 OneDrive 檔案轉移給主管",
"從所有安全性與通訊群組中移除",
"撤銷 OAuth Token 與 API 金鑰",
"從行動裝置清除公司資料",
"封存身分記錄",
"於保留期後排程刪除帳戶"
],
"valid_transitions": ["REHIRE", "DELETED"]
},
"REHIRE": {
"description": "先前離職的員工重新入職",
"automated_actions": [
"重新啟用現有的身分記錄",
"重設憑證並要求重新註冊 MFA",
"根據新職務代碼 (而非先前存取權) 佈建",
"標記為前 30 天需進行加強存取審查"
],
"valid_transitions": ["ACTIVE"]
},
"DELETED": {
"description": "保留期過後永久移除的帳戶",
"automated_actions": [
"刪除 AD 帳戶",
"刪除信箱封存檔",
"從 IGA 中移除身分記錄",
"產生刪除稽核日誌"
],
"valid_transitions": []
}
},
"retention_periods": {
"terminated_to_deleted": "90 天 (預設)",
"mailbox_retention": "保留為共用信箱 90 天",
"onedrive_retention": "主管可存取 30 天,之後封存",
"audit_log_retention": "為符合法規保留 7 年"
}
}
步驟 2:實作權威來源整合
將人資系統連線為身分資料的單一真實來源:
"""
人資來源整合 - Workday 至 IGA 平台連接器
輪詢 Workday 以取得員工生命週期事件並觸發佈建。
"""
import requests
from datetime import datetime, timedelta
import logging
class WorkdayIdentityConnector:
def __init__(self, config):
self.base_url = config["workday_api_url"]
self.tenant = config["tenant"]
self.client_id = config["client_id"]
self.client_secret = config["client_secret"]
self.session = requests.Session()
self.logger = logging.getLogger("workday_connector")
def get_access_token(self):
"""驗證 Workday REST API。"""
token_url = f"{self.base_url}/ccx/oauth2/{self.tenant}/token"
response = self.session.post(token_url, data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
})
response.raise_for_status()
return response.json()["access_token"]
def fetch_worker_changes(self, since_datetime):
"""擷取自上次同步以來的所有員工生命週期事件。"""
headers = {"Authorization": f"Bearer {self.get_access_token()}"}
params = {
"Updated_From": since_datetime.isoformat(),
"Updated_Through": datetime.utcnow().isoformat(),
"Count": 100
}
workers = []
url = f"{self.base_url}/ccx/api/v1/{self.tenant}/workers"
while url:
response = self.session.get(url, headers=headers, params=params)
response.raise_for_status()
data = response.json()
workers.extend(data.get("data", []))
url = data.get("next", None)
params = {}
return workers
def map_lifecycle_event(self, worker):
"""將 Workday 員工資料對應至身分生命週期事件。"""
worker_data = worker.get("workerData", {})
employment = worker_data.get("employmentData", {})
personal = worker_data.get("personalData", {})
event = {
"employee_id": worker.get("id"),
"first_name": personal.get("legalName", {}).get("firstName"),
"last_name": personal.get("legalName", {}).get("lastName"),
"email": worker_data.get("emailAddress"),
"job_code": employment.get("jobProfile", {}).get("id"),
"job_title": employment.get("jobProfile", {}).get("name"),
"department": employment.get("organization", {}).get("name"),
"department_code": employment.get("organization", {}).get("id"),
"manager_id": employment.get("managerId"),
"location": employment.get("location", {}).get("name"),
"cost_center": employment.get("costCenter", {}).get("id"),
"hire_date": employment.get("hireDate"),
"termination_date": employment.get("terminationDate"),
"status": employment.get("status"),
"worker_type": employment.get("workerType"),
}
# 判斷生命週期轉換
if event["status"] == "Active" and event["hire_date"]:
hire_date = datetime.fromisoformat(event["hire_date"])
if hire_date > datetime.utcnow():
event["lifecycle_event"] = "PRE_HIRE"
else:
event["lifecycle_event"] = "JOINER"
elif event["status"] == "Active":
event["lifecycle_event"] = "MOVER" # 部門或角色變更
elif event["status"] == "Terminated":
event["lifecycle_event"] = "LEAVER"
elif event["status"] == "On Leave":
event["lifecycle_event"] = "LEAVE_OF_ABSENCE"
return event
def process_lifecycle_events(self, since_datetime):
"""身分生命週期事件的主要處理迴圈。"""
workers = self.fetch_worker_changes(since_datetime)
events = []
for worker in workers:
event = self.map_lifecycle_event(worker)
events.append(event)
self.logger.info(
f"生命週期事件:{event['lifecycle_event']} for "
f"{event['first_name']} {event['last_name']} "
f"(員工編號:{event['employee_id']})"
)
return events
步驟 3:實作角色探勘與出生權存取
根據職能定義角色以進行自動化佈建:
"""
角色探勘引擎
分析現有存取模式以推導出用於出生權 (自動) 佈建的角色定義。
"""
import pandas as pd
from collections import Counter
from itertools import combinations
class RoleMiningEngine:
def __init__(self, access_data):
"""
access_data: DataFrame,包含欄位
[employee_id, job_code, department, application, entitlement]
"""
self.access_data = access_data
def mine_birthright_roles(self, min_assignment_pct=0.8):
"""
識別應根據職務代碼自動指派的權限。
如果相同職務代碼的 80% 以上使用者擁有某個權限,
則該權限將成為出生權存取。
"""
birthright_roles = {}
for job_code, group in self.access_data.groupby("job_code"):
total_users = group["employee_id"].nunique()
entitlement_counts = group.groupby(
["application", "entitlement"]
)["employee_id"].nunique()
birthright_entitlements = []
for (app, ent), count in entitlement_counts.items():
pct = count / total_users
if pct >= min_assignment_pct:
birthright_entitlements.append({
"application": app,
"entitlement": ent,
"assignment_percentage": round(pct * 100, 1),
"user_count": count
})
if birthright_entitlements:
birthright_roles[job_code] = {
"job_code": job_code,
"total_users": total_users,
"birthright_entitlements": birthright_entitlements
}
return birthright_roles
def detect_role_explosion(self):
"""識別權限過度重疊的角色,表示需要進行整併。"""
roles = self.access_data.groupby("job_code").apply(
lambda x: set(zip(x["application"], x["entitlement"]))
)
overlap_report = []
for (role1, ents1), (role2, ents2) in combinations(roles.items(), 2):
if len(ents1) == 0 or len(ents2) == 0:
continue
overlap = len(ents1 & ents2)
max_size = max(len(ents1), len(ents2))
overlap_pct = overlap / max_size * 100
if overlap_pct > 70:
overlap_report.append({
"role_1": role1,
"role_2": role2,
"role_1_entitlements": len(ents1),
"role_2_entitlements": len(ents2),
"overlapping_entitlements": overlap,
"overlap_percentage": round(overlap_pct, 1),
"recommendation": "CONSOLIDATE" if overlap_pct > 90 else "REVIEW"
})
return sorted(overlap_report, key=lambda x: x["overlap_percentage"], reverse=True)
def find_orphaned_access(self):
"""
找出不再符合任何角色定義的權限。
這些是隨著時間累積的例外狀況。
"""
# 取得出生權定義
birthright = self.mine_birthright_roles(min_assignment_pct=0.5)
orphaned = []
for _, row in self.access_data.iterrows():
job_birthright = birthright.get(row["job_code"], {})
expected_ents = set()
for ent in job_birthright.get("birthright_entitlements", []):
expected_ents.add((ent["application"], ent["entitlement"]))
current_ent = (row["application"], row["entitlement"])
步驟 4:實作存取要求與核准
定義要求入口與核准人指派:
"""
存取要求系統
定義可要求的角色、核准人指派及要求生命週期。
"""
class AccessRequestSystem:
def __init__(self):
self.requestable_roles = {} # role_name -> details
self.approval_assignments = {} # role_name -> approval chain
self.open_requests = []
self.completed_requests = []
def define_requestable_role(self, name, applications, entitlements, justification_required=True):
"""定義一個可供使用者在系統中要求的角色。"""
self.requestable_roles[name] = {
"name": name,
"applications": applications,
"entitlements": entitlements,
"justification_required": justification_required,
"access_profiles": []
}
def set_approval_chain(self, role_name, approvers, escalation_hours=24):
"""為特定角色設定核准人與升級規則。"""
self.approval_assignments[role_name] = {
"role_name": role_name,
"approvers": approvers, # 排序清單:主要、次要...
"escalation_hours": escalation_hours,
"current_approver_index": 0,
"auto_approve_threshold": None # 無
}
def submit_request(self, requester_id, role_name, justification, requested_duration_days):
"""提交新的存取要求。"""
request = {
"request_id": f"REQ-{len(self.open_requests) + len(self.completed_requests) + 1:05d}",
"requester_id": requester_id,
"role_name": role_name,
"justification": justification,
"requested_duration_days": requested_duration_days,
"status": "PENDING_APPROVAL",
"submitted_at": datetime.utcnow().isoformat(),
"approved_at": None,
"expires_at": None
}
self.open_requests.append(request)
return request["request_id"]
def process_approval(self, request_id, approver_id, action, comment=""):
"""處理核准或拒絕決策。"""
for request in self.open_requests:
if request["request_id"] == request_id:
if action == "APPROVE":
request["status"] = "APPROVED"
request["approved_at"] = datetime.utcnow().isoformat()
request["approved_by"] = approver_id
request["approver_comment"] = comment
request["expires_at"] = (
datetime.utcnow() + timedelta(days=request["requested_duration_days"])
).isoformat()
elif action == "REJECT":
request["status"] = "REJECTED"
request["rejected_at"] = datetime.utcnow().isoformat()
request["rejected_by"] = approver_id
request["rejection_reason"] = comment
return request
return None
步驟 5:實作定期存取審查
設計定期稽核週期以重新驗證存取權限:
"""
認證活動管理
排程並協調存取審查。支援管理者、資料擁有者
及應用程式擁有者審查。
"""
from enum import Enum
class ReviewType(Enum):
MANAGER = "manager_review"
DATA_OWNER = "data_owner_review"
APP_OWNER = "application_owner_review"
class CertificationCampaign:
def __init__(self, name, start_date, end_date, review_type):
self.name = name
self.start_date = start_date
self.end_date = end_date
self.review_type = review_type
self.review_items = [] # (user, entitlement, reviewer_id) 清單
self.completed_reviews = []
self.escalations = []
def add_review_item(self, user_id, application, entitlement, reviewer_id):
"""將需要審查的單一存取權限項目新增至活動。"""
self.review_items.append({
"user_id": user_id,
"application": application,
"entitlement": entitlement,
"reviewer_id": reviewer_id,
"status": "PENDING",
"decision": None,
"comment": "",
"reviewed_at": None
})
def submit_decision(self, user_id, application, entitlement, reviewer_id, decision, comment=""):
"""記錄審查決定:確認為有效 / 撤銷 / 暫時豁免。"""
for item in self.review_items:
if (item["user_id"] == user_id and
item["application"] == application and
item["entitlement"] == entitlement and
item["reviewer_id"] == reviewer_id):
item["status"] = "COMPLETED"
item["decision"] = decision
item["comment"] = comment
item["reviewed_at"] = datetime.utcnow().isoformat()
self.completed_reviews.append(item)
return item
return None
def generate_remediation_report(self):
"""為活動中標示為撤銷的所有項目建立動作報告。"""
to_revoke = [
item for item in self.completed_reviews
if item["decision"] == "REVOKE"
]
return {
"campaign_name": self.name,
"total_items": len(self.review_items),
"completed": len(self.completed_reviews),
"pending": len(self.review_items) - len(self.completed_reviews),
"to_revoke": len(to_revoke),
"revocations": to_revoke
}
步驟 6:實作修復與孤兒帳戶清理
確保離職程序會啟動帳戶清理:
"""
存取撤銷引擎
協調多個系統間的存取移除作業並報告孤兒帳戶。
"""
class AccessRevocationEngine:
def __init__(self):
self.connected_systems = {} # system_name -> connector
def register_system(self, name, connector):
"""註冊支援撤銷的目標系統。"""
self.connected_systems[name] = connector
def revoke_application_access(self, user_identifier, applications_to_revoke):
"""撤銷指定應用程式的使用者存取權。"""
results = []
for app in applications_to_revoke:
connector = self.connected_systems.get(app)
if connector:
success = connector.disable_user(user_identifier)
results.append({
"application": app,
"status": "REVOKED" if success else "FAILED"
})
else:
results.append({
"application": app,
"status": "NOT_FOUND"
})
return results
def scan_orphaned_accounts(self):
"""掃描所有已註冊系統中,不存在於人資系統的帳戶。"""
orphaned = []
for system_name, connector in self.connected_systems.items():
users = connector.list_users()
for user in users:
if not self.is_active_employee(user["employee_id"]):
orphaned.append({
"system": system_name,
"username": user["username"],
"employee_id": user["employee_id"],
"last_login": user.get("last_login"),
"days_since_termination": user.get("days_since_termination", 0)
})
return orphaned
def is_active_employee(self, employee_id):
"""根據人資系統檢查員工是否仍在職。"""
# 實作:查閱已快取的人資資料或 API
return False # 預設:未找到即視為非在職(孤兒)
合規注意事項
| 法規 | 生命週期要求 | 實作檢查點 |
|---|---|---|
| SOX | 使用者存取權限必須每季審查;強化變更管理 | 每季認證活動;出生權角色報告 |
| HIPAA | 即時撤銷存取權;審計軌跡保留 6 年 | 離職後 15 分鐘內撤銷;審計日誌保留 7 年 |
| GDPR | 資料最小化原則;在合理時間內刪除 | 自動化刪除排程;刪除前的身分去識別化 |
| PCI DSS | 限制對 Cardholder Data Environment (CDE) 的存取 | CDE 角色的雙重核准;所有存取核准的監控與記錄 |
常見陷阱
- 人資資料延遲:若人資系統無法即時傳送事件,JML 程序將失敗。實作容錯機制與重試邏輯
- 角色定義過於限縮:角色定義若過度特定於個人,會導致角色爆炸。目標:每個職務代碼設定 3-5 個角色
- 核准人疲乏:要求對每一項存取進行核准會淹沒經理人。對低風險項目使用存取套件,並搭配季度認證
- 忽略服務帳戶:許多組織只管理使用者帳戶。將服務帳戶與非個人身分納入治理範圍
- 手動修復:識別出孤兒帳戶後卻未移除,會造成安全風險。實作自動化修復或至少是強制手動處理的待辦事項
監控與警報
# 生命週期流程的監控指標
METRICS = {
"identity_governance": {
"jml_automation_rate": {
"description": "無需人工介入即完成佈建/撤銷的百分比",
"target": "> 95%",
"measurement": "automated_events / total_events * 100"
},
"orphan_account_ratio": {
"description": "孤兒帳戶與活躍帳戶的比率",
"target": "< 1%",
"measurement": "orphan_accounts / total_active_accounts * 100"
},
"certification_completion_rate": {
"description": "在截止日期前完成的審查項目百分比",
"target": "100%",
"measurement": "completed_reviews / total_review_items * 100"
},
"mean_time_to_revoke": {
"description": "從離職事件到所有系統存取撤銷的平均時間",
"target": "< 15 分鐘",
"measurement": "平均時間計算,含所有連接的應用程式"
},
"role_entropy": {
"description": "非出生權權限的百分比 (例外情況)",
"target": "< 10%",
"measurement": "non_birthright_entitlements / total_entitlements * 100"
}
}
}
整合參考
- SailPoint IdentityNow SPSC (Search, Provision, Source, Certification) API
- Saviynt Enterprise Identity Cloud (EIC) REST API
- Microsoft Graph API for Entra ID Governance (access reviews, entitlement management, lifecycle workflows)
- SCIM 2.0 標準用於跨系統使用者佈建
- SAML 2.0 與 OAuth 2.0 用於聯合身分與授權
- LDAP 用於目錄服務查詢