mcpField
I
identity-access-management

identity-access-management

github.com/bagelhole安全1.2k318 次安装23 周前更新
代码仓库

关于 identity-access-management

面向初创团队的身份与访问管理技能,涵盖 Google Workspace、Okta 和 Azure AD(Entra ID)的 SSO、用户配置、MFA 与访问治理,并提供 CLI 和 API 操作示例。

主要功能

  1. 01配置 SAML 和 OIDC SSO
  2. 02SCIM 用户配置
  3. 03设置 MFA 策略
  4. 04基于群组的访问控制
  5. 05审计日志与访问审查
  6. 06员工离职流程

适用场景

  1. 01集中管理应用身份验证
  2. 02准备合规访问证明材料
  3. 03自动化员工入职与离职
  4. 04事件期间撤销访问权限

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill identity-access-management

安装后在 Claude Code 中输入/identity-access-management确认已加载。

详细介绍

初创企业的身份与访问管理

团队人数超过少数几人后,集中式身份管理就不可或缺。本技能介绍如何使用初创企业最常用的三种身份提供商:Google Workspace、Okta 和 Azure AD(Entra ID),配置可投入生产的 SSO、SCIM 配置、MFA 强制策略和访问治理。


1. 何时使用本技能

以下情况可使用本技能:

  • 首次设置 SSO -- 你正从各个应用的独立登录转向集中式身份验证。
  • 准备合规审计 -- SOC 2、ISO 27001 或 HIPAA 要求记录访问控制、MFA 强制策略和审计日志。
  • 团队增长拐点 -- 员工人数即将达到 15-20 人,手动办理入职和离职流程容易出错。
  • 供应商安全问卷 -- 客户询问你的身份安全状况,你需要展示相关控制措施。
  • 事件响应 -- 你需要快速撤销离职用户或账户遭入侵用户在所有系统中的访问权限。

说明你早该采取措施的迹象:

  • 在电子表格或聊天频道中共享密码。
  • 没有记录谁在何时访问了什么内容的集中式审计日志。
  • 离职处理耗时超过一个工作日。
  • 开发人员长期拥有生产环境的管理员访问权限。

2. 将 Google Workspace 用作身份提供商

Google Workspace 是初创企业最常用的起步 IdP。搭配 GAM CLI 工具后,它可实现强大的自动化。

安装 GAM(Google Apps Manager)
# Install GAM on Linux/macOS
bash <(curl -s -S -L https://gam-shortn.appspot.com/gam-install)

# Authorize GAM with your Workspace domain
gam oauth create

# Verify connection
gam info domain
创建组织部门

组织部门(OU)用于控制策略继承和应用访问权限。

# Create OUs for team structure
gam create org "Engineering"
gam create org "Engineering/Backend"
gam create org "Engineering/Frontend"
gam create org "Operations"
gam create org "Operations/IT"
gam create org "Finance"
gam create org "Contractors"

# Move a user into an OU
gam update user [email protected] org "Engineering/Backend"

# List all OUs
gam print orgs
在 Google Workspace 中配置 SAML 应用
# Export the Google IdP metadata (download from Admin Console or use GAM)
# Admin Console: Apps > Web and mobile apps > Add app > Search for app > Download IdP metadata

# For a custom SAML app, you need:
# 1. ACS URL (from the service provider)
# 2. Entity ID (from the service provider)
# 3. Name ID format (usually EMAIL)

# Example: Add a custom SAML app via Admin Console API
gam create samlapp "Internal Dashboard" \
  acs_url "https://dashboard.company.com/saml/acs" \
  entity_id "https://dashboard.company.com" \
  name_id_format "EMAIL" \
  name_id "user.primaryEmail"

# Assign the app to an OU
gam update samlapp "Internal Dashboard" org "Engineering" enabled on

# Verify SAML app status
gam print samlappinfo "Internal Dashboard"
使用 Google Workspace 配置 SCIM
# Enable auto-provisioning for supported apps
# Google Workspace supports automatic user provisioning for apps like:
# Slack, Zoom, Box, Dropbox, Asana, GitHub Enterprise

# List provisioned apps
gam print tokens

# Force sync provisioning for an app
gam sync samlapp "Slack" users

# Bulk create users from CSV
# users.csv format: firstname,lastname,email,org,password
gam csv users.csv gam create user ~email \
  firstname ~firstname lastname ~lastname \
  password ~password org ~org \
  changepassword on
在 Workspace 级别强制实施 MFA
# Enforce 2-step verification for the entire domain
gam update org "/" 2sv enforced

# Enforce 2SV for a specific OU
gam update org "Engineering" 2sv enforced

# Set enforcement date (give users time to enroll)
gam update org "/" 2sv enforced enforceddate 2026-04-15

# Check 2SV enrollment status for all users
gam print users fields isEnforcedIn2Sv,isEnrolledIn2Sv

# Find users who have NOT enrolled in 2SV
gam print users query "isEnrolledIn2Sv=false" fields primaryEmail,name

3. Okta 设置

Okta 为初创企业提供免费套餐(Okta for Startups 计划,最多支持 100 位用户),是需要专用 IdP 的团队的理想选择。

通过 API 初始配置 Okta
# Set your Okta domain and API token
export OKTA_ORG_URL="https://company.okta.com"
export OKTA_API_TOKEN="your-api-token"

# Verify connectivity
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/org" | jq '.companyName'

# Create a user
curl -s -X POST \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  -H "Content-Type: application/json" \
  "${OKTA_ORG_URL}/api/v1/users?activate=true" \
  -d '{
    "profile": {
      "firstName": "Alice",
      "lastName": "Engineer",
      "email": "[email protected]",
      "login": "[email protected]"
    },
    "credentials": {
      "password": { "value": "TempP@ss123!" }
    }
  }' | jq '.id'
创建用于 RBAC 的群组
# Create groups
for group in "Engineering" "Operations" "Finance" "Contractors" "AdminAccess"; do
  curl -s -X POST \
    -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
    -H "Content-Type: application/json" \
    "${OKTA_ORG_URL}/api/v1/groups" \
    -d "{\"profile\": {\"name\": \"${group}\", \"description\": \"${group} team group\"}}" \
    | jq '{id: .id, name: .profile.name}'
done

# Add user to group
USER_ID="00u1abc123"
GROUP_ID="00g1def456"
curl -s -X PUT \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/groups/${GROUP_ID}/users/${USER_ID}"
在 Okta 中添加 SAML 应用
# Create a SAML 2.0 application
curl -s -X POST \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  -H "Content-Type: application/json" \
  "${OKTA_ORG_URL}/api/v1/apps" \
  -d '{
    "name": "custom_saml_app",
    "label": "Internal Dashboard",
    "signOnMode": "SAML_2_0",
    "settings": {
      "signOn": {
        "defaultRelayState": "",
        "ssoAcsUrl": "https://dashboard.company.com/saml/acs",
        "audience": "https://dashboard.company.com",
        "recipient": "https://dashboard.company.com/saml/acs",
        "destination": "https://dashboard.company.com/saml/acs",
        "subjectNameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
        "attributeStatements": [
          {
            "type": "EXPRESSION",
            "name": "email",
            "namespace": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
            "values": ["user.email"]
          },
          {
            "type": "EXPRESSION",
            "name": "groups",
            "namespace": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
            "values": ["getFilteredGroups({\"00g1def456\"}, \"group.name\", 50)"]
          }
        ]
      }
    }
  }' | jq '{id: .id, label: .label, status: .status}'

# Assign group to application
APP_ID="0oa1xyz789"
curl -s -X PUT \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  -H "Content-Type: application/json" \
  "${OKTA_ORG_URL}/api/v1/apps/${APP_ID}/groups/${GROUP_ID}"
Okta MFA 策略
# Create an MFA enrollment policy requiring WebAuthn + TOTP
curl -s -X POST \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  -H "Content-Type: application/json" \
  "${OKTA_ORG_URL}/api/v1/policies" \
  -d '{
    "type": "MFA_ENROLL",
    "name": "Require Strong MFA",
    "status": "ACTIVE",
    "settings": {
      "factors": {
        "webauthn": { "enroll": { "self": "REQUIRED" } },
        "google_otp": { "enroll": { "self": "OPTIONAL" } },
        "okta_email": { "enroll": { "self": "NOT_ALLOWED" } },
        "okta_sms": { "enroll": { "self": "NOT_ALLOWED" } }
      }
    }
  }' | jq '{id: .id, name: .name, status: .status}'

4. Azure AD / Entra ID

使用 Microsoft 365 或 Azure 云服务的初创企业通常会使用 Azure AD(现称 Microsoft Entra ID)。

Azure CLI 设置
# Install Azure CLI and sign in
az login

# Set the default tenant
az account set --subscription "your-subscription-id"

# Verify tenant
az ad signed-in-user show --query '{name:displayName, email:userPrincipalName}'
创建用户和群组
# Create a user
az ad user create \
  --display-name "Alice Engineer" \
  --user-principal-name "[email protected]" \
  --password "TempP@ss123!" \
  --force-change-password-next-sign-in true

# Create security groups
for group in "SG-Engineering" "SG-Operations" "SG-Finance" "SG-Admins"; do
  az ad group create --display-name "$group" --mail-nickname "$group"
done

# Add user to group
USER_OID=$(az ad user show --id "[email protected]" --query id -o tsv)
GROUP_OID=$(az ad group show --group "SG-Engineering" --query id -o tsv)
az ad group member add --group "$GROUP_OID" --member-id "$USER_OID"

# List group members
az ad group member list --group "SG-Engineering" --query '[].{name:displayName, email:userPrincipalName}' -o table
通过 Graph API 配置条件访问策略
# Require MFA for all users accessing cloud apps
# Uses Microsoft Graph API
ACCESS_TOKEN=$(az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv)

curl -s -X POST \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
  -d '{
    "displayName": "Require MFA for all users",
    "state": "enabledForReportingButNotEnforced",
    "conditions": {
      "users": {
        "includeUsers": ["All"],
        "excludeGroups": ["'${BREAKGLASS_GROUP_OID}'"]
      },
      "applications": {
        "includeApplications": ["All"]
      }
    },
    "grantControls": {
      "operator": "OR",
      "builtInControls": ["mfa"]
    }
  }'

# Block legacy authentication (critical for security)
curl -s -X POST \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
  -d '{
    "displayName": "Block legacy authentication",
    "state": "enabled",
    "conditions": {
      "users": { "includeUsers": ["All"] },
      "applications": { "includeApplications": ["All"] },
      "clientAppTypes": ["exchangeActiveSync", "other"]
    },
    "grantControls": {
      "operator": "OR",
      "builtInControls": ["block"]
    }
  }'

5. SSO 集成模式

SAML 与 OIDC 选择指南
因素SAML 2.0OIDC / OAuth 2.0
最适用场景企业级 SaaS 应用SPA、移动应用、API
令牌格式XML 断言JWT 令牌
设置复杂度较高(证书、元数据 XML)较低(客户端 ID + 密钥)
注销不一致(SLO 支持不佳)令牌过期 + 撤销
适用情况应用仅支持 SAML可选择,或需要 API 身份验证

经验法则:如果 SaaS 供应商支持 OIDC,优先选择 OIDC。如果仅支持 SAML,则使用 SAML。切勿通过互联网使用 LDAP。

集成常见 SaaS 应用
Slack Enterprise SSO
# Okta OIDC integration for Slack
# 1. In Okta: Applications > Browse App Catalog > Slack
# 2. Configure with your Slack workspace URL
# 3. Enable SCIM provisioning

# Verify Slack SCIM connection
curl -s -H "Authorization: Bearer ${SLACK_SCIM_TOKEN}" \
  "https://api.slack.com/scim/v2/Users?count=5" | jq '.Resources[].userName'
GitHub Organization SSO
# Configure SAML for GitHub Org (requires GitHub Enterprise Cloud)
# 1. GitHub Org Settings > Authentication security > Enable SAML
# 2. Provide IdP SSO URL, IdP issuer, public certificate from your IdP

# Use GitHub CLI to verify SSO status
gh api orgs/company/credential-authorizations --paginate \
  | jq '.[] | {login: .login, credential_type: .credential_type, authorized_at: .authorized_credential_note}'

# Require SAML SSO for all org members
gh api -X PATCH orgs/company \
  -f saml_enforced=true
AWS SSO(IAM Identity Center)
# Configure AWS IAM Identity Center with external IdP
aws sso-admin list-instances --query 'Instances[0].InstanceArn' --output text

INSTANCE_ARN="arn:aws:sso:::instance/ssoins-1234567890"
IDENTITY_STORE_ID="d-1234567890"

# Create a permission set
aws sso-admin create-permission-set \
  --instance-arn "$INSTANCE_ARN" \
  --name "DeveloperAccess" \
  --description "Read-only + deploy access for engineers" \
  --session-duration "PT8H"

# Attach AWS managed policy to permission set
PERMISSION_SET_ARN="arn:aws:sso:::permissionSet/ssoins-1234567890/ps-abc123"
aws sso-admin attach-managed-policy-to-permission-set \
  --instance-arn "$INSTANCE_ARN" \
  --permission-set-arn "$PERMISSION_SET_ARN" \
  --managed-policy-arn "arn:aws:iam::aws:policy/ReadOnlyAccess"

# Assign group to AWS account with permission set
aws sso-admin create-account-assignment \
  --instance-arn "$INSTANCE_ARN" \
  --target-id "123456789012" \
  --target-type AWS_ACCOUNT \
  --permission-set-arn "$PERMISSION_SET_ARN" \
  --principal-type GROUP \
  --principal-id "a1b2c3d4-5678-90ab-cdef-GROUP001"

6. SCIM 配置

SCIM(跨域身份管理系统)可自动管理 SaaS 应用中的用户生命周期。

SCIM API 示例
# Standard SCIM 2.0 endpoints (most IdPs and SaaS apps follow this)
SCIM_BASE="https://app.example.com/scim/v2"
SCIM_TOKEN="your-scim-bearer-token"

# List users
curl -s -H "Authorization: Bearer ${SCIM_TOKEN}" \
  "${SCIM_BASE}/Users?count=10&startIndex=1" | jq '.Resources[] | {id, userName, active}'

# Create a user via SCIM
curl -s -X POST \
  -H "Authorization: Bearer ${SCIM_TOKEN}" \
  -H "Content-Type: application/scim+json" \
  "${SCIM_BASE}/Users" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "[email protected]",
    "name": { "givenName": "Alice", "familyName": "Engineer" },
    "emails": [{ "primary": true, "value": "[email protected]", "type": "work" }],
    "active": true,
    "groups": []
  }' | jq '{id, userName, active}'

# Deactivate a user via SCIM (PATCH is the standard for partial updates)
USER_SCIM_ID="abc-123-def"
curl -s -X PATCH \
  -H "Authorization: Bearer ${SCIM_TOKEN}" \
  -H "Content-Type: application/scim+json" \
  "${SCIM_BASE}/Users/${USER_SCIM_ID}" \
  -d '{
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
    "Operations": [{ "op": "replace", "value": { "active": false } }]
  }' | jq '{id, userName, active}'

# Delete a user permanently via SCIM
curl -s -X DELETE \
  -H "Authorization: Bearer ${SCIM_TOKEN}" \
  "${SCIM_BASE}/Users/${USER_SCIM_ID}"
SCIM 群组管理
# Create a group via SCIM
curl -s -X POST \
  -H "Authorization: Bearer ${SCIM_TOKEN}" \
  -H "Content-Type: application/scim+json" \
  "${SCIM_BASE}/Groups" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
    "displayName": "Engineering",
    "members": [
      { "value": "user-id-001", "display": "[email protected]" },
      { "value": "user-id-002", "display": "[email protected]" }
    ]
  }' | jq '{id, displayName}'

# Add a member to an existing group
GROUP_SCIM_ID="grp-456"
curl -s -X PATCH \
  -H "Authorization: Bearer ${SCIM_TOKEN}" \
  -H "Content-Type: application/scim+json" \
  "${SCIM_BASE}/Groups/${GROUP_SCIM_ID}" \
  -d '{
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
    "Operations": [{
      "op": "add",
      "path": "members",
      "value": [{ "value": "user-id-003" }]
    }]
  }'

7. MFA 强制策略

WebAuthn / 通行密钥(最强)

WebAuthn(FIDO2)硬件密钥和通行密钥可抵御网络钓鱼,应作为主要 MFA 因素。

# Okta: Enforce WebAuthn as primary factor
curl -s -X PUT \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  -H "Content-Type: application/json" \
  "${OKTA_ORG_URL}/api/v1/org/factors/webauthn" \
  -d '{ "status": "ACTIVE" }'

# Google Workspace: Enforce security keys only (disable SMS/voice)
gam update org "/" 2sv enforced allowedmethods security_key

# Azure AD: Require phishing-resistant MFA via conditional access
# (use the Graph API conditional access endpoint with authenticationStrengths)
curl -s -X POST \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
  -d '{
    "displayName": "Require phishing-resistant MFA for admins",
    "state": "enabled",
    "conditions": {
      "users": { "includeRoles": ["62e90394-69f5-4237-9190-012177145e10"] },
      "applications": { "includeApplications": ["All"] }
    },
    "grantControls": {
      "operator": "OR",
      "authenticationStrength": {
        "id": "00000000-0000-0000-0000-000000000004"
      }
    }
  }'
TOTP 备用配置
# Generate backup codes for users (Okta)
USER_ID="00u1abc123"
curl -s -X POST \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors" \
  -d '{
    "factorType": "token:software:totp",
    "provider": "GOOGLE"
  }' | jq '{id: .id, status: .status}'
MFA 绕过流程(紧急情况)
# Okta: Reset MFA for a locked-out user
USER_ID="00u1abc123"
# List enrolled factors
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors" | jq '.[].factorType'

# Delete a specific factor to allow re-enrollment
FACTOR_ID="fct1abc123"
curl -s -X DELETE \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors/${FACTOR_ID}"

# Google Workspace: Generate backup verification codes
gam user [email protected] update backupcodes

# Azure AD: Require re-registration of MFA methods
az rest --method DELETE \
  --url "https://graph.microsoft.com/v1.0/users/${USER_OID}/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7"

8. 基于角色的访问控制

基于群组的访问模式

将每项应用权限映射到群组,而不是单个用户。

# Naming convention: APP-ROLE
# Examples:
#   aws-developer       -> AWS ReadOnly + deploy
#   aws-admin           -> AWS AdministratorAccess
#   github-engineer     -> GitHub write access
#   github-admin        -> GitHub admin access
#   slack-member        -> Slack standard member
#   pagerduty-oncall    -> PagerDuty responder role

# Okta: Create group rules for automatic assignment based on department
curl -s -X POST \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  -H "Content-Type: application/json" \
  "${OKTA_ORG_URL}/api/v1/groups/rules" \
  -d '{
    "type": "group_rule",
    "name": "Auto-assign engineers to GitHub",
    "conditions": {
      "expression": {
        "value": "user.department == \"Engineering\"",
        "type": "urn:okta:expression:1.0"
      }
    },
    "actions": {
      "assignUserToGroups": { "groupIds": ["GITHUB_ENGINEERS_GROUP_ID"] }
    }
  }'
即时(JIT)访问
# AWS: Grant temporary elevated access using STS assume-role
# The user assumes a role that expires after a set duration
aws sts assume-role \
  --role-arn "arn:aws:iam::123456789012:role/EmergencyAdmin" \
  --role-session-name "alice-incident-2026-03-24" \
  --duration-seconds 3600 \
  | jq '{AccessKeyId: .Credentials.AccessKeyId, Expiration: .Credentials.Expiration}'

# Okta: Create a time-limited group membership (via API scheduled task)
# Add user to admin group
curl -s -X PUT \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/groups/${ADMIN_GROUP_ID}/users/${USER_ID}"

# Schedule removal after 4 hours (use a cron job or automation tool)
echo "0 */4 * * * curl -s -X DELETE -H 'Authorization: SSWS ${OKTA_API_TOKEN}' \
  '${OKTA_ORG_URL}/api/v1/groups/${ADMIN_GROUP_ID}/users/${USER_ID}'" | crontab -
紧急访问账户
# Create break-glass accounts that bypass SSO/MFA for emergency access
# These accounts must be:
# 1. Excluded from conditional access / MFA policies
# 2. Protected with extremely long passwords stored in a physical safe
# 3. Monitored with alerts on any usage

# Azure AD: Create break-glass account
az ad user create \
  --display-name "Break Glass 1" \
  --user-principal-name "[email protected]" \
  --password "$(openssl rand -base64 48)" \
  --force-change-password-next-sign-in false

# Assign Global Administrator role
az ad group member add --group "SG-BreakGlass" --member-id "$BREAKGLASS_OID"

# Set up alert on break-glass sign-in (Azure Monitor)
az monitor activity-log alert create \
  --name "BreakGlass-SignIn-Alert" \
  --resource-group "security-rg" \
  --condition category=Administrative and [email protected] \
  --action-group "/subscriptions/SUB_ID/resourceGroups/security-rg/providers/microsoft.insights/actionGroups/SecurityTeam"

9. 审计与合规

登录审计日志
# Google Workspace: Pull login audit logs
gam report login user all start "2026-03-01" end "2026-03-24" \
  fields "actorEmail,ipAddress,loginType,isSecondFactor,isSuspicious"

# Okta: Query system log for authentication events
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/logs?filter=eventType+eq+\"user.session.start\"&since=2026-03-01T00:00:00Z&limit=100" \
  | jq '.[] | {actor: .actor.displayName, time: .published, outcome: .outcome.result, ip: .client.ipAddress}'

# Azure AD: Pull sign-in logs via Graph API
curl -s -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=createdDateTime ge 2026-03-01T00:00:00Z&\$top=50" \
  | jq '.value[] | {user: .userDisplayName, app: .appDisplayName, status: .status.errorCode, ip: .ipAddress, mfa: .mfaDetail}'
访问权限审查
# List all users and their group memberships for quarterly access review
# Google Workspace
gam print group-members fields email,role > /tmp/access-review-groups.csv

# Okta: Export all users with their app assignments
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users?limit=200" \
  | jq -r '.[] | [.profile.email, .status, .lastLogin] | @csv' > /tmp/okta-users.csv

# For each user, list their app assignments
while IFS= read -r user_id; do
  curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
    "${OKTA_ORG_URL}/api/v1/users/${user_id}/appLinks" \
    | jq -r '.[] | [.label, .linkUrl] | @csv'
done < /tmp/okta-user-ids.txt > /tmp/okta-access-review.csv

# Azure AD: List role assignments
az role assignment list --all --query '[].{principal:principalName, role:roleDefinitionName, scope:scope}' -o table
合规报告
# Count of users with/without MFA enrolled
# Google Workspace
echo "=== MFA Enrollment Report ==="
echo "Enrolled:"
gam print users fields isEnrolledIn2Sv | grep -c True
echo "Not enrolled:"
gam print users fields isEnrolledIn2Sv | grep -c False

# Okta: Users without any MFA factor
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users?filter=status+eq+\"ACTIVE\"&limit=200" \
  | jq '[.[] | select(.credentials.provider.type != "SOCIAL") | .id] | length'

# Check for stale accounts (no login in 90 days)
NINETY_DAYS_AGO=$(date -d "-90 days" +%Y-%m-%dT00:00:00Z 2>/dev/null || date -v-90d +%Y-%m-%dT00:00:00Z)
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users?filter=lastLogin+lt+\"${NINETY_DAYS_AGO}\"&limit=200" \
  | jq '.[] | {email: .profile.email, lastLogin: .lastLogin}'

10. 离职处理

账户停用检查清单

员工离职时,请按此顺序执行。顺序很重要——先撤销会话,再停用账户。

DEPARTING_USER="[email protected]"

# Step 1: Revoke all active sessions immediately
# Okta
USER_ID=$(curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${DEPARTING_USER}" | jq -r '.id')

curl -s -X DELETE \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${USER_ID}/sessions"

# Google Workspace: Revoke tokens and sign out
gam user "${DEPARTING_USER}" signout
gam user "${DEPARTING_USER}" deprovision

# Azure AD: Revoke all refresh tokens
az ad user update --id "${DEPARTING_USER}" --account-enabled false
az rest --method POST \
  --url "https://graph.microsoft.com/v1.0/users/${DEPARTING_USER}/revokeSignInSessions"

# Step 2: Deactivate the user account
# Okta
curl -s -X POST \
  -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${USER_ID}/lifecycle/deactivate"

# Google Workspace
gam update user "${DEPARTING_USER}" suspended on

# Step 3: Transfer data ownership
# Google Workspace: Transfer Drive files
gam user "${DEPARTING_USER}" transfer drive [email protected]

# Google Workspace: Transfer Calendar ownership
gam user "${DEPARTING_USER}" transfer calendar [email protected]

# Step 4: Remove from all groups (prevents future provisioning)
# Okta
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${USER_ID}/groups" \
  | jq -r '.[].id' | while read gid; do
    curl -s -X DELETE \
      -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
      "${OKTA_ORG_URL}/api/v1/groups/${gid}/users/${USER_ID}"
  done

# Step 5: Revoke app-specific tokens
# GitHub: Remove from org
gh api -X DELETE "orgs/company/members/${DEPARTING_USER}"

# Slack: Deactivate via SCIM
SLACK_USER_ID=$(curl -s -H "Authorization: Bearer ${SLACK_SCIM_TOKEN}" \
  "https://api.slack.com/scim/v2/Users?filter=userName+eq+\"${DEPARTING_USER}\"" \
  | jq -r '.Resources[0].id')

curl -s -X PATCH \
  -H "Authorization: Bearer ${SLACK_SCIM_TOKEN}" \
  -H "Content-Type: application/scim+json" \
  "https://api.slack.com/scim/v2/Users/${SLACK_USER_ID}" \
  -d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","value":{"active":false}}]}'

# AWS: Remove SSO access
aws sso-admin delete-account-assignment \
  --instance-arn "$INSTANCE_ARN" \
  --target-id "123456789012" \
  --target-type AWS_ACCOUNT \
  --permission-set-arn "$PERMISSION_SET_ARN" \
  --principal-type USER \
  --principal-id "$AWS_SSO_USER_ID"

# Step 6: Document and log
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | OFFBOARD | ${DEPARTING_USER} | all sessions revoked, account suspended, data transferred to [email protected]" >> /var/log/offboarding-audit.log
离职处理后验证
DEPARTING_USER="[email protected]"

# Verify account is suspended/deactivated
echo "=== Offboarding Verification ==="

# Google Workspace
gam info user "${DEPARTING_USER}" fields suspended | grep -i "suspended: true" && echo "[OK] Google suspended" || echo "[FAIL] Google still active"

# Okta
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/users/${DEPARTING_USER}" \
  | jq -r '.status' | grep -q "DEPROVISIONED" && echo "[OK] Okta deprovisioned" || echo "[FAIL] Okta still active"

# GitHub
gh api "orgs/company/members/${DEPARTING_USER}" 2>&1 | grep -q "404" && echo "[OK] GitHub removed" || echo "[FAIL] GitHub still member"

# Check for any remaining active sessions in audit logs
echo "=== Checking for post-offboard activity ==="
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
  "${OKTA_ORG_URL}/api/v1/logs?filter=actor.alternateId+eq+\"${DEPARTING_USER}\"&since=$(date -u +%Y-%m-%dT%H:%M:%SZ)&limit=10" \
  | jq '.[] | {time: .published, event: .eventType, outcome: .outcome.result}'

快速参考

任务Google WorkspaceOktaAzure AD
创建用户gam create userPOST /api/v1/usersaz ad user create
暂停用户gam update user suspended onPOST /lifecycle/deactivateaz ad user update --account-enabled false
强制启用 MFAgam update org 2sv enforcedMFA enrollment policyConditional access policy
撤销会话gam user signoutDELETE /users/{id}/sessionsrevokeSignInSessions
审计登录记录gam report loginGET /api/v1/logsGET /auditLogs/signIns
SCIM 配置对受支持的应用内置应用集成的 SCIM 选项卡企业应用配置

评论

支持 Markdown