jcode Phone-Server AWS IAM 最小权限设计:从 AdministratorAccess 到三平面角色与防锁死迁移
【免费下载链接】jcodeThe most RAM efficient harness项目地址: https://gitcode.com/GitHub_Trending/jcod/jcode
本文基于 jcode 仓库中 Phone-Server IAM 最小权限评估文档 展开,讲解如何把一条长期持有AdministratorAccess的部署凭据拆解为「日常操作 / 重建供应 / 紧急管理」三个互不越权的权限平面,并完整覆盖各运行时角色的 IAM 策略、只读盘点命令、防锁死迁移步骤与验收标准。读完后你将掌握:如何依据真实 API 调用证据为 EC2、Lambda、API Gateway、Bedrock 编写最小权限策略,以及如何在不锁死账号的前提下安全移除管理员权限并轮换凭据。
背景:一个部署凭据持有 AdministratorAccess 意味着什么
jcode 的 phone-server 是一个自管理的 AWS 云主机:一台 EC2 实例运行jcode serveWebSocket 网关,手机(iOS 应用或 SSH 客户端)无需笔记本中转即可驱动 jcode 会话。部署位于 AWS 账号302154194530、区域us-east-1,实例i-08214cf66cd3f80c7,弹性 IP54.196.207.97(见 部署说明)。
该部署的 IAM 评估起始于一次设计评审,评估日期为 2026-07-12。评估文档的核心结论是:部署用户jade-deploy不应保留AdministratorAccess——一条被泄露的长期部署凭据,当前可以修改身份、关闭成本护栏、外泄数据、创建任意基础设施,并在账号任何位置建立持久化。
文档同时说明了一个务实的现状:实际加固(详见 README 的 "Live deployment" 章节)已经落地并验证,唯一有意保留的缺口是jade-deploy上的AdministratorAccess,它在一条独立的 root/MFA 恢复登录路径被验证之前不会被移除。
目标架构是用「只能 assume-role 的主体 + 三个独立权限平面」替换现状:
JcodePhoneOperator:日常部署与现有栈的维护;JcodePhoneProvisioner:低频的重建操作,受 MFA 门禁、限时(最长会话 1 小时)、限定us-east-1与jcode-phone-*资源、并受运行时边界约束;- 独立的紧急管理员路径:受 MFA 保护、绝不被自动化使用,用于在移除现有管理员挂载时避免锁死。
另外,实例角色上的AmazonBedrockFullAccess也应替换为仅推理权限——从源码看,Bedrock provider 只做模型目录发现和流式对话(ConverseStream),并不需要 Bedrock 管理权限。
证据链:策略必须匹配真实的 API 调用
最小权限策略的前提是精确知道每个组件实际调用了哪些 AWS API。评估文档给出的仓库证据如下,均可在当前仓库中逐一验证:
| 组件 | 实际调用的 AWS API | 源码依据 |
|---|---|---|
| Wake Lambda | ec2:DescribeInstances、ec2:StartInstances | wake-lambda.py 中ec2.describe_instances(...)与ec2.start_instances(InstanceIds=[INSTANCE_ID]) |
| Wake Lambda(SSM 配对路径) | ssm:DescribeInstanceInformation、ssm:SendCommand(AWS-RunShellScript文档)、ssm:GetCommandInvocation | wake-lambda.py 的ssm_online()与fetch_pair_code() |
| Breaker Lambda | ec2:DescribeInstances、ec2:StopInstances、sns:Publish至arn:aws:sns:us-east-1:302154194530:jcode-guard-warn | breaker-lambda.py |
| EC2 实例上的 Bedrock provider | ListFoundationModels、ListInferenceProfiles、流式 Converse(由bedrock:InvokeModelWithResponseStream授权) | jcode-provider-bedrock/src/lib.rs 的目录刷新逻辑与 流式调用 |
Wake Lambda 的 SSM 配对流程值得细看,因为它决定了权限要放大到什么程度:fetch_pair_code()通过ssm.send_command(InstanceIds=[INSTANCE_ID], DocumentName="AWS-RunShellScript", ...)在目标实例上以ec2-user身份执行jcode pair,随后轮询ssm.get_command_invocation直到命令进入终态,再从输出中用正则提取 6 位配对码并生成jcode://pair?...深链。评估文档因此明确提示:ssm:SendCommand应被视为「特权远程代码执行」,必须严格限定到该唯一实例和 AWS 托管文档。
Bedrock 侧还有一个细节:provider 中由环境变量JCODE_BEDROCK_VALIDATE_STS控制的 STS 身份校验(validate_credentials_if_requested)默认关闭,正常路径不需要额外服务权限——这正是评估文档中「provider 的可选 STS 身份校验不需要额外服务权限」这一说法的源码依据。同时,当 AWS 拒绝请求时,provider 会直接提示需要bedrock:InvokeModel、bedrock:InvokeModelWithResponseStream、bedrock:ListFoundationModels、bedrock:ListInferenceProfiles四类权限(错误提示),与本文给出的策略语句一一对应。
声明的命名资源清单
| 类型 | 资源 |
|---|---|
| EC2 实例 | arn:aws:ec2:us-east-1:302154194530:instance/i-08214cf66cd3f80c7 |
| 弹性 IP | 54.196.207.97(分配 ID 需另行盘点) |
| Lambda | jcode-phone-wake |
| Lambda | jcode-guard-breaker |
| API Gateway v2 | 8c3wp4cbag |
| SNS | jcode-guard-stop与jcode-guard-warn |
| CloudWatch 告警 | jcode-bedrock-tokens-warn、jcode-bedrock-tokens-stop(无效的账单告警已被 Budget/SNS 熔断路径替代) |
| Budget | jcode-dev-monthly-cost |
| Bedrock 模型路由 | us.anthropic.claude-opus-4-6-v1 |
实态核查结果
评估完成后,账号已通过jcode-bedrockprofile 做了全面盘点:运行时角色名、Lambda 角色、EC2 实例与安全组、弹性 IP、Budget 订阅者、CloudWatch 告警、日志保留、API Gateway stage、S3/DynamoDB 资源、访问密钥元数据均直接验证过。当前实态为:Wake Lambda 已改用 SSM 配对,所有公网入站 EC2 端口已关闭,根 EBS 卷已加密,CloudTrail 与 Access Analyzer 已启用,旧的部署密钥处于停用状态。这与 README 安全说明 中「实例角色为推理权限 + SSM 受管实例访问」「7644 端口不再公开、legacy pair 服务已停用」的描述一致。
目标身份设计:三平面与 assume-only 主体
人类访问
首选终态:
- 人类操作者使用 IAM Identity Center 或其他联合身份;
- operator 与 provisioner 两个角色的 assume 都要求 MFA;
- 不为人类访问创建长期密钥;
jade-deploy仅在迁移期间保留,观察期结束后删除。
如果过渡期必须保留 IAM 用户,则:
jade-deploy无控制台密码、无任何服务权限;- 唯一权限是对两个部署角色的
sts:AssumeRole; - 它不能修改自己、策略、访问密钥、MFA 设备或角色信任策略。
jade-deploy的 assume-only 策略:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AssumePhoneServerRolesOnly", "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "arn:aws:iam::302154194530:role/jcode-phone/JcodePhoneOperator", "arn:aws:iam::302154194530:role/jcode-phone/JcodePhoneProvisioner" ] } ] }两个角色的信任策略都应要求 MFA(对人类 IAM 主体):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::302154194530:user/jade-deploy" }, "Action": "sts:AssumeRole", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" }, "NumericLessThan": { "aws:MultiFactorAuthAge": "3600" } } } ] }JcodePhoneProvisioner的最大会话时长设为 1 小时;两个角色都不允许修改自己的信任策略或权限。
对无人值守 CI,使用独立的 OIDC 联合角色,限定到具体仓库、分支/环境与 workflow;不要为了 CI 而放宽人类角色的 MFA 信任。
运行时策略:实例与每个 Lambda 各用独立执行角色
总原则:实例与每个 Lambda 使用各自的执行角色,运行时绝不复用部署角色。
EC2 实例:仅推理权限
用以下客户管理策略替换AmazonBedrockFullAccess。注意两点:List*操作必须Resource: "*";推理资源刻意只覆盖已配置的 Claude Opus 4.6 跨区 profile 及其底层 foundation model——因为跨区推理可能路由到us-east-1之外,只写单区域的 foundation-model ARN 会失效。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DiscoverBedrockModels", "Effect": "Allow", "Action": [ "bedrock:ListFoundationModels", "bedrock:ListInferenceProfiles" ], "Resource": "*" }, { "Sid": "InvokeConfiguredClaudeProfile", "Effect": "Allow", "Action": [ "bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream" ], "Resource": [ "arn:aws:bedrock:us-east-1:302154194530:inference-profile/us.anthropic.claude-opus-4-6-v1", "arn:aws:bedrock:*::foundation-model/anthropic.claude-opus-4-6-v1:0" ] } ] }应用前用bedrock list-inference-profiles核实确切的 inference-profile ARN 与底层模型 ID。如果部署的是 AWS 所有的 profile、或底层模型版本后缀不同,应替换为返回的 ARN,而不是放宽模型名通配。区域通配符是刻意为之,因为 US 跨区推理 profile 可路由到多个 US 区域。测试完成后,若所有生产路径都只用ConverseStream,可移除bedrock:InvokeModel——保留它只是很小的兼容性余地,不是管理权限。provider 的可选 STS 身份校验在正常操作中不需要额外服务权限(见上文源码分析)。
这与源码行为吻合:refresh_catalog() 先调list_foundation_models()收集模型 ID、推断 ON_DEMAND / INFERENCE_PROFILE 支持类型、标记 LEGACY 模型,再调list_inference_profiles()收集 profile 与优选路由;流式推理 走converse_stream()。目录刷新即/refresh-model-list的底层实现,缺少bedrock:ListInferenceProfiles时源码会明确报错要求授权(错误提示)。
Wake Lambda
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ReadTargetInstanceState", "Effect": "Allow", "Action": "ec2:DescribeInstances", "Resource": "*" }, { "Sid": "StartTargetInstanceOnly", "Effect": "Allow", "Action": "ec2:StartInstances", "Resource": "arn:aws:ec2:us-east-1:302154194530:instance/i-08214cf66cd3f80c7" }, { "Sid": "WriteOwnLogs", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:us-east-1:302154194530:log-group:/aws/lambda/jcode-phone-wake:*" } ] }日志组应在开通期创建并配置保留策略,而不是在运行时授予*上的logs:CreateLogGroup。
当前部署已采用 SSM 唤醒/配对实现(legacy 的公网:7644路径已禁用),因此还要追加 SSM 语句:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ReadManagedInstanceStatus", "Effect": "Allow", "Action": "ssm:DescribeInstanceInformation", "Resource": "*" }, { "Sid": "RunPairCommandOnTargetOnly", "Effect": "Allow", "Action": "ssm:SendCommand", "Resource": [ "arn:aws:ec2:us-east-1:302154194530:instance/i-08214cf66cd3f80c7", "arn:aws:ssm:us-east-1::document/AWS-RunShellScript" ] }, { "Sid": "ReadPairCommandResult", "Effect": "Allow", "Action": "ssm:GetCommandInvocation", "Resource": "*" } ] }该变体同时要求 EC2 角色上有 SSM 受管实例策略。建议它与 Bedrock 策略分开维护,在 CloudTrail 中核对确切的 Systems Manager 调用,并在操作可行时从AmazonSSMManagedInstanceCore收窄。始终记住:ssm:SendCommand是特权远程代码执行,范围必须锁定到那一台实例和 AWS 托管文档。wake-lambda.py 中ssm_online()与fetch_pair_code()的调用正是这三条语句对应的最小集合。
Breaker Lambda
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ReadTargetInstanceState", "Effect": "Allow", "Action": "ec2:DescribeInstances", "Resource": "*" }, { "Sid": "StopTargetInstanceOnly", "Effect": "Allow", "Action": "ec2:StopInstances", "Resource": "arn:aws:ec2:us-east-1:302154194530:instance/i-08214cf66cd3f80c7" }, { "Sid": "PublishGuardNotification", "Effect": "Allow", "Action": "sns:Publish", "Resource": "arn:aws:sns:us-east-1:302154194530:jcode-guard-warn" }, { "Sid": "WriteOwnLogs", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:us-east-1:302154194530:log-group:/aws/lambda/jcode-guard-breaker:*" } ] }对应 breaker-lambda.py 的逻辑:检查实例状态 → 若在运行则停止 → 向jcode-guard-warn主题发布「jcode circuit breaker fired」消息。该 Lambda 由 SNSjcode-guard-stop订阅触发(预算超支 100% 时由 AWS Budget 通知)。
日常操作角色:JcodePhoneOperator
这个角色能更新和诊断现有资源,但不能创建身份、创建任意计算、终止实例、释放 EIP、删除护栏或修改角色信任。以下策略中的<...>占位值应在只读盘点后替换。注意 API Gateway 资源形式刻意使用不含账号 ID 的管理 ARN。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DescribePhoneServerInfrastructure", "Effect": "Allow", "Action": [ "ec2:DescribeInstances", "ec2:DescribeInstanceStatus", "ec2:DescribeAddresses", "ec2:DescribeSecurityGroups", "ec2:DescribeVolumes", "cloudwatch:DescribeAlarms", "cloudwatch:GetMetricData", "cloudwatch:GetMetricStatistics", "cloudwatch:ListMetrics" ], "Resource": "*" }, { "Sid": "ReadExistingFunctions", "Effect": "Allow", "Action": [ "lambda:GetFunction", "lambda:GetFunctionConfiguration", "lambda:GetPolicy" ], "Resource": [ "arn:aws:lambda:us-east-1:302154194530:function:jcode-phone-wake", "arn:aws:lambda:us-east-1:302154194530:function:jcode-phone-wake:*", "arn:aws:lambda:us-east-1:302154194530:function:jcode-guard-breaker", "arn:aws:lambda:us-east-1:302154194530:function:jcode-guard-breaker:*" ] }, { "Sid": "OperateExistingInstance", "Effect": "Allow", "Action": [ "ec2:StartInstances", "ec2:StopInstances", "ec2:RebootInstances", "ec2:ModifyInstanceAttribute" ], "Resource": "arn:aws:ec2:us-east-1:302154194530:instance/i-08214cf66cd3f80c7" }, { "Sid": "DeployExistingFunctions", "Effect": "Allow", "Action": [ "lambda:UpdateFunctionCode", "lambda:UpdateFunctionConfiguration", "lambda:PublishVersion", "lambda:CreateAlias", "lambda:UpdateAlias", "lambda:DeleteAlias", "lambda:InvokeFunction" ], "Resource": [ "arn:aws:lambda:us-east-1:302154194530:function:jcode-phone-wake", "arn:aws:lambda:us-east-1:302154194530:function:jcode-phone-wake:*", "arn:aws:lambda:us-east-1:302154194530:function:jcode-guard-breaker", "arn:aws:lambda:us-east-1:302154194530:function:jcode-guard-breaker:*" ] }, { "Sid": "ReadAndPatchExistingHttpApiRoot", "Effect": "Allow", "Action": [ "apigateway:GET", "apigateway:PATCH" ], "Resource": "arn:aws:apigateway:us-east-1::/apis/8c3wp4cbag" }, { "Sid": "MaintainExistingHttpApiChildren", "Effect": "Allow", "Action": [ "apigateway:GET", "apigateway:POST", "apigateway:PUT", "apigateway:PATCH", "apigateway:DELETE" ], "Resource": "arn:aws:apigateway:us-east-1::/apis/8c3wp4cbag/*" }, { "Sid": "PassInstanceRoleToEc2Only", "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::302154194530:role/jcode-phone/runtime/JcodePhoneInstance", "Condition": { "StringEquals": { "iam:PassedToService": "ec2.amazonaws.com" } } }, { "Sid": "PassLambdaRolesToLambdaOnly", "Effect": "Allow", "Action": "iam:PassRole", "Resource": [ "arn:aws:iam::302154194530:role/jcode-phone/runtime/JcodePhoneWakeLambda", "arn:aws:iam::302154194530:role/jcode-phone/runtime/JcodePhoneBreakerLambda" ], "Condition": { "StringEquals": { "iam:PassedToService": "lambda.amazonaws.com" } } }, { "Sid": "ReadPhoneLogs", "Effect": "Allow", "Action": [ "logs:DescribeLogStreams", "logs:GetLogEvents", "logs:FilterLogEvents" ], "Resource": [ "arn:aws:logs:us-east-1:302154194530:log-group:/aws/lambda/jcode-phone-wake:*", "arn:aws:logs:us-east-1:302154194530:log-group:/aws/lambda/jcode-guard-breaker:*" ] }, { "Sid": "MaintainGuardTopics", "Effect": "Allow", "Action": [ "sns:GetTopicAttributes", "sns:ListSubscriptionsByTopic", "sns:Publish" ], "Resource": [ "arn:aws:sns:us-east-1:302154194530:jcode-guard-stop", "arn:aws:sns:us-east-1:302154194530:jcode-guard-warn" ] }, { "Sid": "ReadNamedBudget", "Effect": "Allow", "Action": "budgets:ViewBudget", "Resource": "arn:aws:budgets::302154194530:budget/jcode-dev-monthly-cost" } ] }推荐的进一步收紧:
- 若日常维护从不改动关机行为、实例类型、source/destination check 或附加的 profile,移除
ec2:ModifyInstanceAttribute; lambda:AddPermission、lambda:RemovePermission、sns:Subscribe、告警变更、预算变更留在 MFA 门禁的 provisioner 路径——这些操作可能导致调用暴露、通知外泄或成本控制被禁用;- API Gateway 的
DELETE只允许在/apis/8c3wp4cbag/*之下,日常角色不能删除 API 根; - 绝不授予
cloudwatch:DeleteAlarms、budgets:DeleteBudget、sns:DeleteTopic、ec2:TerminateInstances、ec2:ReleaseAddress或任何 IAM 写操作。
重建/供应角色:用 CloudFormation 执行角色收敛建删权限
重建天然需要多个服务的创建/删除权限,这些权限必须远离日常角色。最易维护的做法是把栈放进 CloudFormation、CDK 或 Terraform,让人类只调用一个命名的栈部署角色。
推荐模型:
- 人类
JcodePhoneProvisioner:仅jcode-phone-server*栈的 CloudFormation 操作、只读诊断,以及仅对JcodePhoneCloudFormationExecution、条件iam:PassedToService = cloudformation.amazonaws.com的iam:PassRole; JcodePhoneCloudFormationExecution:承载下述服务权限,只能被 CloudFormation 使用;- 所有创建的资源打标签
Project=jcode-phone-server与ManagedBy=cloudformation; - 所有创建的运行时角色位于路径
/jcode-phone/runtime/之下,且必须携带JcodePhoneRuntimeBoundary权限边界。
人类 provisioner 策略:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ManageNamedPhoneStack", "Effect": "Allow", "Action": [ "cloudformation:CreateStack", "cloudformation:UpdateStack", "cloudformation:DeleteStack", "cloudformation:DescribeStacks", "cloudformation:DescribeStackEvents", "cloudformation:DescribeStackResources", "cloudformation:GetTemplate", "cloudformation:GetTemplateSummary", "cloudformation:ListStackResources", "cloudformation:CreateChangeSet", "cloudformation:DescribeChangeSet", "cloudformation:ExecuteChangeSet", "cloudformation:DeleteChangeSet", "cloudformation:ValidateTemplate" ], "Resource": [ "arn:aws:cloudformation:us-east-1:302154194530:stack/jcode-phone-server*/*", "arn:aws:cloudformation:us-east-1:302154194530:changeSet/jcode-phone-server*/*" ] }, { "Sid": "ValidateNewTemplate", "Effect": "Allow", "Action": "cloudformation:ValidateTemplate", "Resource": "*" }, { "Sid": "PassPhoneCloudFormationExecutionRole", "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::302154194530:role/jcode-phone/JcodePhoneCloudFormationExecution", "Condition": { "StringEquals": { "iam:PassedToService": "cloudformation.amazonaws.com" } } } ] }CloudFormation 执行角色应只允许如下服务/操作包络:
| 服务 | 必需的重建操作 | 范围/护栏 |
|---|---|---|
| EC2 | 启动/终止那台服务器;创建/打标签卷与 ENI;创建/管理一个安全组;分配/关联/释放一个 EIP;修改关机行为;描述 AMI/子网/VPC | us-east-1;请求/资源标签Project=jcode-phone-server;仅批准的实例类型;强制 IMDSv2;批准的 VPC/子网 |
| IAM | 创建/更新/删除三个运行时角色与一个实例 profile;put/delete 内联策略;pass role | 仅限路径/jcode-phone/runtime/;CreateRole要求边界 ARN;绝不允许修改 deploy/provisioner/emergency 角色 |
| Lambda | 创建/更新/删除两个命名函数;版本/别名;权限 | 函数 ARN 前缀jcode-phone-*与jcode-guard-breaker*;项目标签 |
| API Gateway v2 | 创建/更新/删除一个 HTTP API、integration、route、stage | 项目标签与栈所有权 |
| SNS | 创建/管理/删除jcode-guard-stop与jcode-guard-warn;订阅 | 精确的主题名 ARN |
| CloudWatch | 创建/更新/删除四个命名告警 | 精确的告警名 ARN |
| Logs | 创建/配置/删除两个 Lambda 日志组 | 精确的/aws/lambda/...ARN;必须设置保留 |
| Budgets | 创建/更新/删除jcode-dev-monthly-cost | 精确的预算 ARN |
lambda:AddPermission/RemovePermission、sns:Subscribe/Unsubscribe、CloudWatch 告警变更与预算变更由执行角色(而非日常操作角色)持有,从而保住集成与护栏维护能力,同时不把这些高影响操作放进日常凭据。
CloudFormation 执行角色不应持有通用的iam:*、organizations:*、account:*、sts:AssumeRole、kms:*、不受限的s3:*、不受限的secretsmanager:*,也不能修改 provisioner、operator、emergency 角色、自身角色或自身边界。
权限边界不是授权,而是上限。用它作为运行时角色的最大值,并配合各自的窄内联策略。边界只应允许:
- 实例的 Bedrock 目录发现与已配置模型推理权限;
- Wake Lambda 的启动/描述与自身日志权限;
- Breaker Lambda 的停止/描述、告警发布与自身日志权限。
由于各角色权限不同,边界可以取它们的并集;每个角色的内联策略必须保持为更窄的子集。边界中还应显式拒绝 IAM、STS assume-role、Organizations、账号管理和访问密钥操作,作为纵深防御。
迁移前必须完成的只读盘点
以下命令在现有管理员会话重新认证后运行,全部只读、不做任何变更:
aws sts get-caller-identity aws iam list-attached-user-policies --user-name jade-deploy aws iam list-user-policies --user-name jade-deploy aws iam list-access-keys --user-name jade-deploy aws iam get-account-authorization-details > phone-server-iam-before.json aws ec2 describe-instances --instance-ids i-08214cf66cd3f80c7 --region us-east-1 aws ec2 describe-addresses --public-ips 54.196.207.97 --region us-east-1 aws lambda get-function --function-name jcode-phone-wake --region us-east-1 aws lambda get-function --function-name jcode-guard-breaker --region us-east-1 aws apigatewayv2 get-api --api-id 8c3wp4cbag --region us-east-1 aws sns list-topics --region us-east-1 aws cloudwatch describe-alarms --alarm-name-prefix jcode- --region us-east-1 aws budgets describe-budget --account-id 302154194530 --budget-name jcode-dev-monthly-cost aws bedrock list-inference-profiles --type-equals SYSTEM_DEFINED --region us-east-1另需盘点至少 30 天的 CloudTrail 使用情况;只有当某个 action 对应已知的部署/维护操作时才加入策略。Access Analyzer 基于 CloudTrail 的策略生成可以作为第二意见,但不能替代人工审查,因为很少用到的恢复操作可能根本不出现在日志里。
防锁死迁移与凭据轮换:15 步操作序列
- 先备好恢复路径。验证账号 root 邮箱、root MFA 与恢复联系人。建立或验证一条独立的、MFA 保护的紧急管理员路径(首选 IAM Identity Center)。它必须独立于
jade-deploy、无访问密钥、且在触碰AdministratorAccess之前完成测试。 - 捕获状态。导出 IAM 授权明细、用户策略挂载、角色信任策略、访问密钥元数据、资源 ARN 与标签;记录正被替换的
AdministratorAccess挂载的精确位置。 - 创建运行时策略与角色。创建窄化的实例、wake、breaker 角色。此时不切换工作负载;用 IAM Access Analyzer 或
aws accessanalyzer validate-policy校验策略文档。 - 创建 operator 与 provisioner 角色。加上 MFA 门禁信任;确保两个角色都不能修改自己、紧急路径、权限边界或任意身份。
- 管理员挂载仍在时授予 assume-only 访问。把小的
sts:AssumeRole策略挂到jade-deploy,但暂时保留AdministratorAccess。 - 测试角色入口。用独立 profile/会话逐个 assume 各角色,确认
aws sts get-caller-identity报告的是角色 ARN;确认 operator 对 EC2、Lambda、API Gateway、日志、SNS、告警与预算的读权限。 - 模拟每一个必需的 API 调用。用
iam:SimulatePrincipalPolicy或策略模拟器跑本文的动作/资源矩阵;明确测试应被拒绝的控制项——如创建用户、挂载AdministratorAccess、终止任意实例、删除护栏。 - 用金丝雀变更路径测试,不碰生产。通过 provisioner 部署再删除一个带标签的小型金丝雀栈(尽量使用相同的资源类别)。对 operator,更新一个可丢弃的 Lambda 别名或金丝雀函数,而不是调用
jcode-guard-breaker——它会停掉生产实例,绝不能把它当 IAM 测试。 - 逐个切换运行时角色。先换 Lambda 执行角色,验证日志与无副作用的 wake 状态检查;再替换 EC2 实例 profile,跑一次真实的 Bedrock 流式请求。旧角色保持完整但先不挂载,直到验证完成。
- 轮换凭据载体。首选:工作站切到 IAM Identity Center 与角色 profile。过渡 IAM 用户方案:创建第二把
jade-deploy访问密钥,在新 profile 下配置并验证角色 assume。绝不先覆盖唯一可用的 profile——IAM 用户最多两把密钥。 - 仅在独立恢复路径成功后才解绑
AdministratorAccess。解绑前立刻在独立浏览器/profile 中确认紧急管理员路径;然后从jade-deploy解绑AdministratorAccess。同一变更窗口内不删除用户、密钥、旧运行时角色或旧策略。 - 跑解绑后检查。重新 assume operator 与 provisioner,重跑所有只读检查与安全部署检查,验证 phone-server 健康端点,在约定的维护窗口内验证 wake 行为、配对流程与一次完整的 Bedrock 流式请求。
- 停用旧密钥但先不删除。新访问路径稳定工作至少 24–72 小时后把旧密钥标记为 inactive;监控 CloudTrail 中使用该 access-key ID 的尝试与预期工作流上的
AccessDenied。 - 观察后再删除。再观察 7–14 天、确认无需回滚后,删除 inactive 密钥、清理废弃策略/角色,并在联合身份稳定后彻底删除
jade-deploy。 - 每季度复审。使用访问密钥最近使用数据、CloudTrail、Access Analyzer、凭据报告、告警/预算测试;移除未使用的 action;演练紧急路径但不把它用于日常工作。
回滚规则
如果移除管理员后某个必需操作失败,停下来,用独立的紧急管理员路径修正那个窄化角色——不要把AdministratorAccess重新挂回日常用户当默认修复手段。也绝不把已有的 STS 会话当作唯一回滚机制:策略变更与会话过期都可能使该假设失效。
与 IAM 相邻的其他安全发现
这些不是 IAM 替换所必需的,但对部署有实质影响:
- Wake 密钥原本内嵌在 Lambda 源码里并出现在查询字符串中。查询串 token 可能出现在浏览器历史、日志、分析工具、截图与 referrer 里。应存入 Secrets Manager 或 SSM Parameter Store,改用 header 或短时签名请求比对,并只授予 wake Lambda 对那一个密钥的读权限。仓库中的 wake-lambda.py 已体现改进方向:token 走 URL fragment(浏览器不会把它发给 API Gateway),JavaScript 把它换成
Authorization: Bearerheader 存于 session storage,hmac.compare_digest恒定时间比对,且 legacy?t=链接一次性 302 重定向到 fragment 形式。 - 端口
7644曾公开暴露且 Lambda 以明文 HTTP 调用。应优先使用私有 VPC 路径、SSM 中介配对或仅 tailnet 访问;若必须保留公网,应限制安全组源并加 TLS。当前部署已通过 SSM Run Command 生成配对码,该端口不再公开(legacy 的 jcode-pair.service 已停用)。 - 配对服务曾以 root 运行并调用
sudo -u ec2-user;systemd unit 应改为专用用户,加文件系统保护、NoNewPrivileges与窄范围的 sudo 规则。 - 即使修好了
jade-deploy,实例角色当前的AmazonBedrockFullAccess也偏宽。 - 为
AttachUserPolicy、PutUserPolicy、CreateAccessKey、UpdateAssumeRolePolicy、PassRole以及对 emergency/部署角色的任何变更添加 CloudTrail 告警。
验收标准
迁移完成的判据是:
jade-deploy没有AdministratorAccess,也没有除角色 assume 之外的直接 AWS 服务权限;- 日常部署与维护通过
JcodePhoneOperator成功完成; - 带标签的完整重建可以通过 MFA 门禁的 provisioner/CloudFormation 路径执行;
- 运行时角色只包含上文记录的 API 调用;
- 独立的紧急管理员路径经过测试;
- 旧访问密钥经历「停用 → 观察 → 删除」;
- CloudTrail 中没有必需操作的意外
AccessDenied,观察期内也没有旧密钥的使用记录。
小结
这份评估文档的价值在于把「最小权限」落成了可执行的工程流程:从源码级 API 证据出发写策略(ec2:StartInstances只给一台实例、ssm:SendCommand只给一台实例加一个 AWS 文档、Bedrock 推理只给一个 profile 加通配区域的 foundation model ARN),用三平面角色把日常、重建、应急三种风险隔离开,再用 15 步防锁死序列完成AdministratorAccess的移除与凭据轮换。整个方案的所有资源 ARN、Lambda 代码与 Bedrock provider 行为都能在 phone-server 目录 与 jcode-provider-bedrock 源码 中逐一对证,适合作为「长期部署凭据降权」的完整参考实现。
【免费下载链接】jcodeThe most RAM efficient harness项目地址: https://gitcode.com/GitHub_Trending/jcod/jcode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考