如何在香港服务器的Windows Server系统中配置Just Enough Administration (JEA),减少权限滥用风险?

夜半两点,香港荔枝角机房的值班群里“叮”地一声:有一台前端节点 CPU 飙高、IIS 停摆。年轻同事小李第一反应是拿着域管凭据 RDP 上去——十分钟后服务是起来了,但第二天我在变更审计里看见:他顺手“优化”了几个计划任务,还把一个旧版脚本删了。业务没受影响,心却凉了半截——这已经不是第一次“为了快”而越权。就在那一刻,我下定决心,把香港区的运维入口全部收束到 JEA(Just Enough Administration) 上:不给不该给的权限,也不给“顺手做点别的”的机会。
下面这份笔记,就是我在香港机房落地 JEA 的完整过程。它不是泛泛而谈的白皮书,而是带有灰尘、指纹和坑洞的真实记录:从规划、落地到排坑;从硬件、网络到策略、脚本;新手能照做,老手也能挑刺。
我们的现场与边界条件
机房概况(HK1 机房,B 区列头柜)
| 项 | 规格/现状 |
|---|---|
| 机型 | Dell R650 / HPE DL360 混部 |
| CPU / 内存 | Xeon Silver/Gold(12–24C)/ 128–256 GB |
| 存储 | 系统盘 SATA SSD + 数据盘 NVMe U.2(RAID1/10) |
| 网卡 | 2×10GbE(生产/管理面分离) |
| OS | Windows Server 2019/2022 Datacenter(GUI + 少量 Core) |
| PowerShell | 5.1(内置)+ 7.4(部分运维机) |
| 身份 | 单域(hk.example.local),集中 AD |
| 访问 | 跳板/Bastion + WinRM/PowerShell Remoting(强制 HTTPS) |
| 审计 | Windows 事件日志 + PowerShell Transcript + SIEM 汇聚 |
目标
- 把“会的人”变成“会 + 只会做该做的事的人”。
- 修复“救火=越权”的文化,固化到入口与工具链里。
- 减少域管/本地管理员的流转,最小权限、可审计、可回滚。
为什么是 JEA(而不是单纯靠制度/脚本)
- 最小暴露面:JEA 用受限端点暴露“刚好够”的命令/参数/对象,跑在 RunAs 虚拟账户或受控服务账户之下。
- 黑匣子审计:每个会话强制转录(Transcript)、事件日志齐全;谁什么时候在端点里干了什么,一清二楚。
- 好落地:Windows Server 2016+ 原生能力;服务端坚持用 Windows PowerShell 5.1 + WinRM,客户端可用 5.1 或 7.x。
- 可组合:角色能力(Role Capability)像乐高一样拼装,能适配我们 HK 区“Web 维护 / SQL 只读 / 巡检”等典型场景。
角色设计与权限矩阵(先定边界,再写配置)
| 角色 | 典型操作 | 暴露命令/限制 | RunAs 身份 | 备注 |
|---|---|---|---|---|
| WebMaintainer | 重启 AppPool、站点状态、导出 IIS 日志 | Restart-WebAppPool(仅指定池名)、Get-WebSite、Get/Restart-Service(仅 W3SVC)等 |
本地虚拟账户 + Administrators(仅此端点内生效) | 避免给本地管理员实权,靠端点限制命令面 |
| SQLReadOnly | 查只读 DMV、导出备份状态 | Invoke-Sqlcmd(只读实例/数据库)、Get-Service(SQL 服务状态) |
受限域组(DB 只读) | 若需跨机资源,使用专用服务账户端点 |
| SysInspector | 查事件、系统信息、磁盘、网卡 | Get-EventLog/Get-WinEvent、Get-Process、Get-NetAdapter、Get-Volume 等 |
本地虚拟账户 + Event Log Readers | 只读巡检,无写操作 |
| EmergencyReset | 重置某些服务、清理特定目录 | Restart-Service(白名单)、Remove-Item(指定路径、阻止 -Recurse) |
本地虚拟账户 + Administrators | 仅应急窗口开放 |
原则
- “能做成事”的最小集合;每个命令都配参数白名单。
- 涉及跨主机/网络资源的场景,单独做“服务账户端点”(避免虚拟账户的网络不可见限制)。
前置准备(一次性)
1) WinRM/PowerShell Remoting(HTTPS 强制)
# 在目标服务器(示例:HK-WEB-01)上
Enable-PSRemoting -Force
# 申请/生成证书(建议用企业 CA;这里示例自签)
$cert = New-SelfSignedCertificate -DnsName "hk-web-01.hk.example.local" `
-CertStoreLocation Cert:\LocalMachine\My
$thumb = $cert.Thumbprint
# 创建 HTTPS Listener
winrm create winrm/config/Listener?Address=*+Transport=HTTPS `
"@{Hostname=`"hk-web-01.hk.example.local`"; CertificateThumbprint=`"$thumb`"}"
# 防火墙允许 5986
New-NetFirewallRule -DisplayName "WinRM over HTTPS 5986" `
-Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow
经验:跨机房/跨运营商环境下,只开 5986(HTTPS),并把 WinRM 限制在管理网段;证书务必匹配主机名,否则客户端会抛证书错误。
2) 目录与审计
New-Item -ItemType Directory -Path C:\JEA\Transcripts -Force | Out-Null
# 建议组策略同时开启 Script Block Logging + Module Logging
动手:以“WebMaintainer”端点为例(IIS 运维)
我们把所有 IIS 维护动作集中到一个 JEA 端点 WebMaintenance 里,授权给域组 HK\IIS-Ops。
Step 1:创建模块骨架(承载 Role Capability)
$ModuleName = 'HKG.JEA.WebOps'
$ModuleVer = '1.0.0'
$ModuleRoot = "C:\Program Files\WindowsPowerShell\Modules\$ModuleName\$ModuleVer"
New-Item -ItemType Directory -Path "$ModuleRoot\RoleCapabilities" -Force | Out-Null
New-ModuleManifest -Path "$ModuleRoot\$ModuleName.psd1" `
-RootModule '' `
-Author 'HK Ops' -CompanyName 'Example Ltd' `
-ModuleVersion $ModuleVer `
-Description 'JEA RoleCapabilities for HK Web operations'
经验:RoleCapability 文件必须放在 …\Modules\<Module>\RoleCapabilities\ 下,文件名与能力名一致,否则 JEA 找不到。
Step 2:写 Role Capability(IISMaintainer.psrc)
$rcPath = "$ModuleRoot\RoleCapabilities\IISMaintainer.psrc"
New-PSRoleCapabilityFile -Path $rcPath `
-Author 'HK Ops' -CompanyName 'Example Ltd' `
-Description 'Allow safe IIS maintenance operations' `
-ModulesToImport @('WebAdministration') `
-VisibleCmdlets @(
@{ Name = 'Get-Website' },
@{ Name = 'Get-WebAppPoolState' },
@{ Name = 'Restart-WebAppPool';
Parameters = @{ Name='Name'; ValidateSet=@('DefaultAppPool','ApiPool','PortalPool') } },
@{ Name = 'Get-Service';
Parameters = @{ Name='Name'; ValidateSet=@('W3SVC','WAS') } },
@{ Name = 'Restart-Service';
Parameters = @{ Name='Name'; ValidateSet=@('W3SVC','WAS') } },
# 只读系统信息
'Get-Process','Get-ComputerInfo','Get-ChildItem','Get-Item','Test-Path'
) `
-VisibleProviders @('FileSystem','Registry') `
-AliasesToExport @()
经验:把 Restart-Service 这类“危险但必要”的 cmdlet 用 ValidateSet 锁死到白名单服务;不要暴露 Stop-Process、Remove-Item -Recurse 等可破坏性强的命令。
Step 3:写 Session Configuration(WebMaintenance.pssc)
$pssc = 'C:\JEA\WebMaintenance.pssc'
New-PSSessionConfigurationFile -Path $pssc `
-SchemaVersion '2.0.0.0' `
-SessionType RestrictedRemoteServer `
-LanguageMode NoLanguage `
-TranscriptDirectory 'C:\JEA\Transcripts' `
-RunAsVirtualAccount `
-RunAsVirtualAccountGroups @('Administrators') `
-ModulesToImport @('WebAdministration') `
-RoleDefinitions @{
'HK\IIS-Ops' = @{ RoleCapabilities = 'IISMaintainer' }
}
解释:
- RunAsVirtualAccount:会话内会生成一个临时本地“虚拟管理员”,它只在这个端点中有效。结合受限命令面,既“拿得到权限”,又“用不出祸”。
- NoLanguage:禁用脚本语言特性(如 if/foreach),进一步缩小攻击面。
- TranscriptDirectory:所有会话完整转录,便于审计/回溯。
Step 4:注册端点
Register-PSSessionConfiguration -Name 'WebMaintenance' -Path $pssc -Force
# 生效验证
Get-PSSessionConfiguration -Name WebMaintenance | Format-List Name,Permission,StartupScript
Step 5:授权与连通性测试
# 把需要做 IIS 维护的同事加入域组 HK\IIS-Ops(由 AD 管理同学操作)
# 客户端(跳板机或本机 PowerShell 5.1/7.x 均可)
Enter-PSSession -ComputerName hk-web-01.hk.example.local `
-ConfigurationName WebMaintenance `
-Credential (Get-Credential 'HK\alice.ops')
# 端点内验证(能做、不能做,都要试)
Get-Command # 只能看到暴露的命令
Restart-WebAppPool -Name ApiPool # ✅ 允许
Restart-Computer # ❌ 被拒绝(命令不可见)
Stop-Process -Name w3wp # ❌ 被拒绝(命令不可见)
再做两个端点:只读 SQL 与系统巡检(要点)
1) SQL 只读(SqlReadOnly.psrc / SqlReadOnly.pssc)
服务器上安装 SqlServer PowerShell 模块(或用 Invoke-Sqlcmd 的所在模块)。
VisibleCmdlets 限定在查询、状态类,禁止任何 DDL/DML。
RunAs 选择:
若仅本机+命名管道:仍可用虚拟账户,给其在实例内映射只读登录。
若需访问域内远端 SQL:不要用虚拟账户,改建服务账户端点(RunAs 用受控域账号;或专门做 gMSA 方案,超出本文展开)。
示例(片段):
# Role Capability 片段
-VisibleCmdlets @(
@{ Name='Invoke-Sqlcmd';
Parameters = @{
Name='ServerInstance'; ValidateSet=@('HKSQL01','HKSQL02\REPORT');
Name='Database'; ValidateSet=@('ReportDB','BillingRO')
} },
'Get-Service' # 仅查看 SQL 服务
)
2) 系统巡检(SysInspector.psrc / SysInspector.pssc)
暴露 Get-WinEvent、Get-Process、Get-NetAdapter、Get-Volume、Get-CimInstance -ClassName Win32_* 等。
只读为原则,不开放任何写入类命令。
适合一线 NOC/外包同事排障时使用。
日常操作脚本(给一线同事的“傻瓜按钮”)
在跳板机上,我配了一些客户端函数,自动连到对应端点,把复杂参数藏起来:
function Restart-ApiPoolHK {
param([string]$Server)
Enter-PSSession -ComputerName $Server -ConfigurationName WebMaintenance `
-Credential $global:OpsCred -Authentication Negotiate -UseSSL
Restart-WebAppPool -Name ApiPool
Exit-PSSession
}
function Tail-IISLogHK {
param([string]$Server)
Enter-PSSession -ComputerName $Server -ConfigurationName SysInspect `
-Credential $global:OpsCred -Authentication Negotiate -UseSSL
Get-ChildItem 'C:\inetpub\logs\LogFiles\W3SVC1' | Sort-Object LastWriteTime -Desc | Select-Object -First 1 |
ForEach-Object { Get-Content $_.FullName -Tail 100 }
Exit-PSSession
}
经验:把“进入端点—执行—退出”封装成函数,降低一线同事误用的概率。
审计与回溯(出事了,怎么还原真相)
Transcript:C:\JEA\Transcripts\ 下每次会话都有时间戳/用户/端点名。
事件日志:Microsoft-Windows-PowerShell/Operational、Security(4624/4634/4688 等)结合 SIEM。
变更挂钩:把关键命令(如 Restart-Service W3SVC)的 Transcript 片段推送到变更平台,做到“操作即记录”。
我踩过的坑(以及我是怎么填上的)
“我按文档放了 .psrc,端点里就是没生效?”
根因:RoleCapability 文件不在 …\Modules\<Module>\RoleCapabilities\ 或文件名与能力名不一致。
处理:检查路径与文件名;Get-Module -ListAvailable 看模块是否可见;必要时 Remove-Module 后重载。
WinRM HTTPS 连不上,证书报错
根因:CN/SAN 与主机名不匹配,或链路上被做了 SSL Inspection。
处理:用企业 CA 下发带 FQDN 的证书;跳板机信任该 CA;避免经过会做中间人代理的出口。
虚拟账户访问不了文件共享/SQL
说明:JEA 的虚拟账户是本地身份,默认没有域资源访问权。
处理:把需要跨资源的场景单独做服务账户端点(Register-PSSessionConfiguration -RunAsCredential 指定受控域账号),或改业务流程为“目标机推送结果”,避免端点去“拉”。
PowerShell 7 客户端偶发兼容性问题
情况:极少数老模块(如早期 WebAdministration)在 PowerShell 7 上调用 WSMan 端点有边角问题。
处理:服务端坚持 5.1,客户端若出兼容告警,改用 5.1 连接该端点;或在 7.x 中 pwsh -WorkingDirectory . -NoLogo 明确 -UseWindowsPowerShell(Windows 兼容层)。
参数白名单没写好,被用户“聪明地”绕过
例:你只白名单了 -Name,却忘了限制 -DisplayName 或 -InputObject。
处理:对同义/等效参数也设限制;必要时用包装函数把危险参数屏蔽掉,再把包装函数暴露到 RoleCapability。
变更与回滚(稳字当头)
更新端点:改 .psrc/.pssc 后,Register-PSSessionConfiguration -Name <Name> -Path <pssc> -Force。
临时下线:Disable-PSSessionConfiguration -Name <Name>(不断开已有会话,新会话被阻止)。
彻底撤销:Unregister-PSSessionConfiguration -Name <Name>;删除模块目录即可回到“未部署 JEA”状态。
应急后门:保留一条仅域管可用的 DefaultShell 端点(或物理带外/IPMI + 本地管),防止“误配置把自己锁外面”。
成效与对比(上线 3 周的度量)
| 指标 | 上线前(域管/RDP) | 上线后(JEA 端点) |
|---|---|---|
| 越权变更/周 | 2–3 起 | 0 起 |
| 故障恢复中位时长 | 18 分钟 | 9 分钟 |
| 审计取证时间 | 2 小时 | 10 分钟 |
| 域管密码使用频率 | 高 | 极低(仅变更窗口) |
心得:速度并没有因为“受限”而变慢,反而因为“入口标准化 + 脚本化 + 审计可见”整体效率更高。
附:一个“服务账户端点”的简化示例(跨资源场景)
当确实需要访问远端 SQL/文件共享时,为该目的单独做端点与域服务账户(权限最小化、仅该端点可用):
$pssc = 'C:\JEA\SqlRO-Svc.pssc'
New-PSSessionConfigurationFile -Path $pssc `
-SessionType RestrictedRemoteServer -LanguageMode NoLanguage `
-TranscriptDirectory 'C:\JEA\Transcripts' `
-ModulesToImport @('SqlServer') `
-RoleDefinitions @{ 'HK\DB-RO' = @{ RoleCapabilities = 'SQLReadOnly' } }
# 用受控域账号注册(账号权限仅授予所需数据库的只读)
$cred = Get-Credential 'HK\svc_jea_sqlro'
Register-PSSessionConfiguration -Name 'SqlReadOnly' -Path $pssc -RunAsCredential $cred -Force
注意:服务账户的生命周期与凭据轮换要纳入 PAM/密管系统;这个端点只开放给需要的人,且只暴露只读命令。
结尾:一次“可预期的安全事故”
三周后,一个周五晚上的小演练,我们让新人在 WebMaintenance 端点里“尝试优化”IIS。他先后敲了 Restart-WebAppPool -Name PortalPool(成功)、Stop-Process w3wp(失败)、Remove-Item C:\inetpub\wwwroot -Recurse(失败)。我看着 SIEM 大屏上跳出的三条审计记录,忽然松了口气。JEA 没把人变聪明,但它逼迫我们把“能做什么、不能做什么”说清楚,并锁进系统里。
夜已深,机房的风还是冷,但我知道,即使半夜再来一通告警,大家也不会再为了“快”去借一把能开所有门的钥匙。
参考清单(你可以直接复用/改名)
模块路径:C:\Program Files\WindowsPowerShell\Modules\HKG.JEA.WebOps\1.0.0\
- HKG.JEA.WebOps.psd1(模块清单)
- RoleCapabilities\IISMaintainer.psrc
- RoleCapabilities\SQLReadOnly.psrc
- RoleCapabilities\SysInspector.psrc
端点配置:
- C:\JEA\WebMaintenance.pssc
- C:\JEA\SqlRO-Svc.pssc(如需)
- C:\JEA\Transcripts\(会话转录输出)
如果你准备在大量服务器上推广,建议把 .psrc/.pssc 做成一个内部模块,用 CI/CD(Argo/Jenkins/Az DevOps)按 OU/标签分发,端点注册脚本按批次执行;再配合 GPO 强制日志策略,形成“配置即合规”。
——以上,就是我在香港服务器里落地 JEA 的全部过程和坑点。希望你看完,既能今天就照抄上线,也能在你们的场景里,拼出属于自己的“刚刚好权限”