Conversation
新增功能:
- 创建 kubeconfig_export 插件,支持为集群生成和导出 kubeconfig
- 提供管理界面查看 kubeconfig 模板列表
- 支持按命名空间限制导出 kubeconfig
- 支持下载 YAML 格式的 kubeconfig 文件
优化改进:
- 修改 /admin/plugin/list API 支持分页查询
- 添加 page 和 perPage 参数处理
- 优化插件列表前端显示,支持分页导航
- 提升插件列表的可浏览性和用户体验
技术细节:
- 插件使用 manager_api.go 的 dao.BuildParams 获取分页参数
- 集群类路由:/k8s/cluster/{clusterID}/plugins/kubeconfig-export/*
- 管理类路由:/mgm/plugins/kubeconfig-export/*
- 前端 AMIS JSON 配置支持分页展示
将所有插件路径从连字符形式(kubeconfig-export)统一为下划线形式(kubeconfig_export),保持与其他插件的一致性。 修改内容: - 前端页面访问路径:/plugins/kubeconfig_export/management - API 路由前缀:/plugins/kubeconfig_export - 管理路由:/mgm/plugins/kubeconfig_export/* - 删除多余的前端目录(ui/public/pages/plugins/kubeconfig_export)
修改内容:
1. 添加导出 kubeconfig 的 API 路由:POST /mgm/plugins/kubeconfig_export/kubeconfig/{id}/export
2. 添加根据 ID 获取 kubeconfig 的路由:GET /mgm/plugins/kubeconfig_export/kubeconfig/{id}
3. 修改管理路由注册条件,支持已启用、运行中和已停止状态的插件注册管理路由
4. 实现导出 kubeconfig 功能,支持按 namespace 限制和自定义文件名
修改内容: 1. 将导出 kubeconfig 的 API 从 POST 改为 GET 请求 2. 后端使用 c.Query() 从 URL 参数获取 namespace 和 role 3. 前端使用 onEvent 事件处理,通过 open actionType 打开新窗口下载文件 4. 优化导出文件名生成逻辑,支持按 namespace 和 role 命名 5. 删除多余的 ui/public/pages/plugins/kubeconfig_export 目录
修改内容: 1. 使用 actionType: 'url' 和 blank: true 在新窗口打开下载链接 2. 用户可以在对话框中填写 namespace 和 role 参数 3. 提交后在新窗口触发下载,避免前端处理 blob 响应的问题 4. 简化导出流程,提升用户体验
修改内容: 1. 后端使用 application/octet-stream 作为 Content-Type 2. 前端使用 actionType: 'ajax' + dataType: 'blob' + downloadName 实现文件下载 3. 添加高级导出按钮,支持按 namespace 和 role 导出 4. 文件名包含 cluster 和 namespace 信息 5. 删除多余的 ui/public/pages/plugins/kubeconfig_export 目录
修改内容: 1. 从 link 类型改为 button 类型,使用 ajax 请求 2. 设置 responseType: blob 让 AMIS 正确处理二进制响应 3. 设置 downloadName 指定下载文件名 4. 使用 AJAX 请求可以携带当前用户的 token,避免被拦截
修改内容: 1. 在 filename 周围添加双引号,符合 RFC 5987 标准 2. 格式从 attachment; filename=name 改为 attachment; filename="name" 3. 确保浏览器正确解析文件名并触发下载
修改内容: 1. 直接使用 c.Writer.Header().Set() 设置响应头 2. 避免在 c.Data() 中重复设置 Content-Type 3. 确保 Content-Disposition 头在写入内容之前正确设置 4. 使用直接写入响应的方式,确保 headers 正确传递
修改内容:
1. 使用 onEvent + actionType: custom + script 方式
2. 通过 document.createElement('a') 创建下载链接
3. 调用 a.click() 触发下载,在同一页面上下文中执行
4. 继承当前页面的认证 token,避免被拦截
5. 清理临时 DOM 元素
这种方式绕过了 AMIS 框架对 blob 响应的处理,使用浏览器原生的下载功能
参考 FileOperations 下载实现,创建自定义 React 组件:
1. 创建 KubeconfigDownloadButton.tsx 自定义组件
2. 在 AMIS 中注册为 type: 'kubeconfigDownloadButton'
3. 前端配置直接使用 type 引用自定义组件
4. 通过 data prop 接收当前行数据(id, cluster, display_name)
5. 使用 localStorage.getItem('token') 获取认证 token
6. 创建 a 元素并调用 click() 触发下载
这种方式:
- 正确传递数据(cluster 名称不会被占位符替代)
- 自动携带认证 token,避免登录问题
- 使用浏览器原生下载功能,绕过 AMIS 限制
- 新增 sanitizeFilename 函数,清理可能破坏响应头的特殊字符 - 移除引号、分号、反斜杠等可能导致 CRLF 注入的字符 - 允许字母、数字、中文、空格、连字符、下划线、点 - 修正 metadata.go 中的描述错别字(生产 -> 生成)
将spec参数从查询字符串改为路径参数,并使用base64编码确保特殊字符安全传递。避免URL中特殊字符导致的解析错误。
将路由中的 kubeconfig-export 统一改为 kubeconfig_export,以保持与插件目录名一致
重构插件定时任务管理API,将路由参数从base64编码的spec更改为索引值。前端JSON配置和后台API端点同步更新,通过索引直接定位插件配置中的cron表达式,消除base64编解码的复杂性和潜在错误。
将 run_once 接口从 URL 路径传参改为 POST body 传参:
- 路由简化为 /plugin/cron/{name}/run_once
- spec 参数通过 JSON body 提交,避免 URL 编码问题
- 增加验证确保 spec 属于该插件的 cron 列表
- 使用 slices.Contains 简化查找逻辑
- 新增kubeconfig_export插件的管理页面JSON配置 - 将body的overflow从hidden改为auto以允许滚动 - 为main内容区域设置最小高度 - 隐藏#translate元素
📝 WalkthroughSummary by CodeRabbit发布说明
Walkthrough本变更扩展了插件系统的核心管理功能,修改了cron规范的索引化管理和列表分页,并新增完整的kubeconfig导出插件实现,包括后端API、生命周期管理、前端UI和路由注册。 Changes
Sequence DiagramsequenceDiagram
participant Client as 前端客户端
participant API as 后端API服务
participant Service as ClusterService
participant Models as 数据库
participant KubeCmd as clientcmd库
rect rgba(100, 150, 200, 0.5)
Note over Client,KubeCmd: Kubeconfig 生成与导出流程
Client->>API: POST /plugins/kubeconfig_export/generate<br/>(clusterID, namespace)
activate API
API->>Service: GetClusterByID(clusterID)
activate Service
Service->>Models: 查询集群信息
Models-->>Service: 返回集群配置
Service-->>API: 返回集群对象
deactivate Service
API->>KubeCmd: clientcmd.Load(kubeconfig)
activate KubeCmd
KubeCmd-->>API: 返回Config对象
deactivate KubeCmd
API->>API: 覆盖当前Context中的namespace
API->>KubeCmd: clientcmd.Write(config)
activate KubeCmd
KubeCmd-->>API: 返回序列化后的kubeconfig
deactivate KubeCmd
API-->>Client: 返回JSON(cluster元数据 + kubeconfig)
deactivate API
end
rect rgba(200, 100, 150, 0.5)
Note over Client,API: Kubeconfig 文件导出流程
Client->>API: GET /mgm/plugins/kubeconfig_export/kubeconfig/{id}/export
activate API
API->>Models: GetOne(id)
Models-->>API: 返回kubeconfig记录
API->>KubeCmd: clientcmd.Load + 命名空间覆盖
API->>KubeCmd: clientcmd.Write
KubeCmd-->>API: 返回YAML字节流
API->>API: sanitizeFilename(display_name, namespace, role)
API-->>Client: HTTP 200<br/>Content-Disposition: attachment<br/>filename=kubeconfig.yaml<br/>body: YAML字节
deactivate API
Client->>Client: 触发浏览器下载
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (9)
ui/src/styles/global.scss (1)
16-18: 建议限制#translate的作用域。全局
display: none容易误伤其他页面或第三方翻译入口,建议限定到目标页面容器下。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/styles/global.scss` around lines 16 - 18, The global rule hiding `#translate` is too broad; restrict its scope by removing or changing the global selector and instead place a scoped rule like .<target-page>-container `#translate` { display: none; } or move the rule into the target component's stylesheet so only the specific page container (e.g. .conversation-container or `#chat-page`) affects `#translate`; update references to the `#translate` selector accordingly and delete the broad global `#translate { display: none; }` from global.scss.pkg/plugins/modules/kubeconfig_export/lifecycle.go (1)
11-13:cancelStart字段已声明但未使用
cancelStart字段在结构体中声明,但在Start()和Stop()方法中都没有使用。参考pkg/plugins/modules/demo/lifecycle.go中的实现模式,如果插件不需要后台任务,应移除此字段以保持代码清晰;如果将来需要,则应正确实现。♻️ 移除未使用的字段(如果不需要后台任务)
// KubeconfigExportLifecycle Kubeconfig导出插件生命周期实现 type KubeconfigExportLifecycle struct { - cancelStart context.CancelFunc }同时可以移除未使用的
"context"导入。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/plugins/modules/kubeconfig_export/lifecycle.go` around lines 11 - 13, KubeconfigExportLifecycle 中声明的 cancelStart 字段未被使用:删除结构体中的 cancelStart 字段并同时移除未使用的 "context" 导入;参考 pkg/plugins/modules/demo/lifecycle.go 的模式,若未来需要后台任务再恢复并在 Start() 中创建 context.WithCancel 并在 Stop() 中调用 cancelStart(),否则保持结构体和导入精简。ui/src/components/Amis/custom/KubeconfigDownloadButton.tsx (2)
13-36: try/catch 无法捕获实际下载失败当前的 try/catch 块只能捕获同步错误。使用
<a>元素触发下载时,HTTP 错误(如 401、404、500)不会被捕获,用户会看到"下载中"的成功消息,但实际下载可能失败。如果需要更可靠的错误处理,建议改用
fetchAPI 并配合Blob下载。♻️ 使用 fetch 实现更可靠的下载
const handleDownload = () => { - try { - const token = localStorage.getItem('token') || ''; - const params = new URLSearchParams({ - token: token - }).toString(); - - let url = `/mgm/plugins/kubeconfig_export/kubeconfig/${data.id}/export`; - if (params) { - url += `?${params}`; - } - - const a = document.createElement('a'); - a.href = url; - a.download = `${data.cluster}-kubeconfig.yaml`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - - message.success('Kubeconfig 文件正在下载...'); - } catch (e) { - message.error('下载失败,请重试'); - } + const token = localStorage.getItem('token') || ''; + const url = `/mgm/plugins/kubeconfig_export/kubeconfig/${data.id}/export?token=${encodeURIComponent(token)}`; + + fetch(url) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + return response.blob(); + }) + .then(blob => { + const blobUrl = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = blobUrl; + a.download = `${data.cluster}-kubeconfig.yaml`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(blobUrl); + message.success('Kubeconfig 文件下载成功'); + }) + .catch(() => { + message.error('下载失败,请重试'); + }); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/components/Amis/custom/KubeconfigDownloadButton.tsx` around lines 13 - 36, The current handleDownload function triggers a download via an anchor element so network errors (401/404/500) aren't caught by the try/catch; change handleDownload to perform a fetch to the same endpoint (including the token from localStorage either in headers or query params), check response.ok, read the response as a Blob, create an object URL and programmatically download it (using data.cluster for the filename and data.id for the URL), revoke the object URL after use, and call message.success or message.error based on the fetch/response outcome so real HTTP failures are reported to the user.
21-23: 条件判断if (params)始终为真
params变量是由URLSearchParams.toString()生成的字符串,即使token为空字符串,params也会是"token=",因此这个条件判断始终为真。♻️ 简化代码
- let url = `/mgm/plugins/kubeconfig_export/kubeconfig/${data.id}/export`; - if (params) { - url += `?${params}`; - } + const url = `/mgm/plugins/kubeconfig_export/kubeconfig/${data.id}/export?${params}`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/components/Amis/custom/KubeconfigDownloadButton.tsx` around lines 21 - 23, The current check if (params) is always true because params is produced by URLSearchParams.toString() (e.g., "token="), so change the condition to only append the query string when it's non-empty (e.g., params !== '' or params.length > 0), or alternatively check the URLSearchParams instance for actual keys before calling toString(); update the logic in the KubeconfigDownloadButton where params and URLSearchParams.toString() are used to ensure you only add `?${params}` when params contains at least one real key-value pair.ui/public/pages/plugins/kubeconfig_export/management.json (1)
1-63: CRUD 配置缺少分页设置当前 CRUD 组件没有配置分页参数(如
perPage、footerToolbar),当模板数量较多时可能影响性能和用户体验。建议参考ui/public/pages/admin/plugins/list.json中的分页配置模式。♻️ 建议添加分页配置
{ "type": "crud", "api": "get:/mgm/plugins/kubeconfig_export/templates", + "perPage": 10, "columns": [ ... ], "headerToolbar": [ "reload" ], + "footerToolbar": [ + "statistics", + "pagination" + ], "itemActions": [ ... ] }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/public/pages/plugins/kubeconfig_export/management.json` around lines 1 - 63, The CRUD page config (the "crud" component with api "get:/mgm/plugins/kubeconfig_export/templates") lacks pagination settings; add pagination by specifying perPage (and optional perPageOptions), enable a footerToolbar that includes "pagination" (and optionally "statistics"), and ensure the API is queried with the expected pageField/perPageField if the backend requires them; update the CRUD config keys (e.g., perPage, perPageOptions, footerToolbar, pageField, perPageField) so the list supports server-side pagination and better performance.pkg/plugins/modules/kubeconfig_export/mgm/mgm.go (3)
148-150: 未使用的description参数
description参数被读取但未使用(赋值给_)。如果该参数确实不需要,建议移除相关代码以避免混淆;如果计划后续使用,建议添加 TODO 注释说明。♻️ 移除未使用的参数读取
// 获取请求参数(从 URL 查询参数获取) namespace := c.Query("namespace") role := c.Query("role") - _ = c.Query("description") // description 参数暂未使用🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/plugins/modules/kubeconfig_export/mgm/mgm.go` around lines 148 - 150, The code reads a query parameter into an unused variable ("_ = c.Query(\"description\")"); remove that unused query read or, if the description will be used later, replace the discard with a proper variable (e.g., description := c.Query("description")) and add a TODO comment explaining its planned use; locate the snippet where namespace := c.Query("namespace") and role := c.Query("role") are set in mgm.go and modify the line handling the "description" parameter accordingly.
199-201: 写入响应时忽略错误
c.Writer.Write(exportedKubeConfig)的返回错误被忽略。虽然在大多数情况下连接断开时无法恢复,但记录错误有助于问题排查。♻️ 记录写入错误
// 写入文件内容 c.Writer.WriteHeader(200) - _, _ = c.Writer.Write(exportedKubeConfig) + if _, err := c.Writer.Write(exportedKubeConfig); err != nil { + klog.V(6).Infof("写入 kubeconfig 响应失败: %v", err) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/plugins/modules/kubeconfig_export/mgm/mgm.go` around lines 199 - 201, The response write currently ignores the error from c.Writer.Write(exportedKubeConfig); update the code in mgm.go to capture the returned (n, err) from c.Writer.Write, check err, and log any write failures (including contextual info like requested resource or bytes written) using the package's logger (or c.Logger/context logger) so write errors are not silently dropped; ensure any logging call includes the error value and relevant identifiers for debugging.
78-91: GetClusterKubeconfig 功能未实现此端点被注册到路由中但返回"功能暂未实现"错误。如果当前版本不需要此功能,建议暂时不注册此路由,或添加 TODO 注释说明实现计划。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/plugins/modules/kubeconfig_export/mgm/mgm.go` around lines 78 - 91, GetClusterKubeconfig currently returns a "not implemented" error; either remove/unregister the route or implement the handler: in GetClusterKubeconfig validate clusterID (already done), query your cluster store/DB for the kubeconfig by clusterID, handle not-found and DB errors by calling amis.WriteJsonError with the error, and on success return the kubeconfig payload using the project's JSON success helper (replace the current amis.WriteJsonError("功能暂未实现")); if you intentionally leave it unimplemented add a clear TODO comment above GetClusterKubeconfig explaining the planned implementation and remove the route registration until implemented.pkg/plugins/modules/kubeconfig_export/cluster/cluster.go (1)
200-202: 建议记录写入错误写入响应体时的错误被忽略。虽然在
WriteHeader(200)之后无法更改响应状态,但记录错误对于调试连接中断等问题仍有帮助。♻️ 建议记录写入错误
// 写入文件内容 c.Writer.WriteHeader(200) - _, _ = c.Writer.Write(exportedKubeConfig) + if _, err := c.Writer.Write(exportedKubeConfig); err != nil { + klog.V(6).Infof("写入 kubeconfig 响应失败: %v", err) + return + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go` around lines 200 - 202, 在将 exportedKubeConfig 写入响应时忽略了 Write 返回的错误;在调用 c.Writer.Write(exportedKubeConfig) 后捕获返回的 err 并记录下来(使用当前作用域内的日志对象,例如已有的 logger/klog/processLogger 或者函数内可用的日志变量),同时保留已发送的状态码;具体修改点:在调用 c.Writer.WriteHeader(200) 之后、c.Writer.Write(exportedKubeConfig) 的地方检查错误并调用日志记录函数记录包含上下文(如 "failed to write kubeconfig response" 和 exportedKubeConfig 长度或目标集群标识)的错误信息。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go`:
- Around line 14-20: GenerateRequest currently declares Duration and Role but
they are not enforced when handing back kubeconfigs; either remove these fields
or mark them TODO, or implement enforcement: in the handler that processes
GenerateRequest and returns the kubeconfig (the code that currently echoes role
in the response), create a ServiceAccount, create/apply a Role or ClusterRole
and corresponding RoleBinding/ClusterRoleBinding according to
GenerateRequest.Role, obtain a token for that ServiceAccount via TokenRequest
(or create a secret) with expiration based on GenerateRequest.Duration, build a
kubeconfig that uses that ServiceAccount token and return it (and ensure the
response.role reflects the actually applied role). Update the GenerateRequest
struct or add TODO comments if you choose not to implement enforcement.
- Around line 99-122: The comment above sanitizeFilename is inaccurate: it says
"不包括 CR LF" but the implementation removes all control characters with code < 32
(which includes CR and LF); update the comment for sanitizeFilename to
accurately state that control characters (ASCII < 0x20, including CR and LF) and
DEL are removed, or otherwise adjust the wording to reflect that CR/LF are also
filtered by the function.
- Around line 224-230: 在处理导出 kubeconfig 的请求时,amis.WriteJsonError(c,
fmt.Errorf("功能暂未实现")) 发送错误响应后缺少 return,导致后续仍会执行 klog.V(6).Infof("请求导出 kubeconfig
ID %s", idStr) 并记录不一致的日志;在该处理函数中(定位到调用 amis.WriteJsonError 的位置),在该调用语句之后立即添加
return,确保在发送错误响应后函数立即返回,不再执行后续的 klog.V(6).Infof 或其他逻辑。
- Around line 133-140: The handler currently tries to parse GET request
parameters using ShouldBindJSON which fails for GET; update the binding to use
query parameters instead by replacing ShouldBindJSON(&req) with query-binding
(e.g., use ShouldBindQuery(&req) or manually read
c.Query("Namespace")/c.Query("Role")/c.Query("Duration")/c.Query("Description")/c.Query("Format"))
so ExportRequest fields are populated for GET; alternatively if you prefer body
JSON keep the JSON binding but change the route in cluster_api.go to POST
(adjust the route registration at the site referenced in cluster_api.go line 17)
so the handler and ExportRequest remain consistent.
- Around line 39-44: 当前 GetKubeConfigByID / ExportKubeConfig 直接按 ID 从 DB 读取
kubeconfig(通过 service.ClusterService().GetClusterByID)但没有任何权限校验,且
EnsureSelectedClusterMiddleware 在 mgm 路由上被白名单跳过,导致任意登录用户可导出任意 kubeconfig;请在这些
mgm 处理器中加入授权检查(例如在 GetKubeConfigByID 和 ExportKubeConfig 中使用当前用户信息调用一个权限方法如
ClusterService().CanAccessCluster(user, clusterID) 或检查用户所属项目/租户与 clusterID
关联),在无权限时返回 403 并停止导出;或者把该插件路由从 EnsureSelectedClusterMiddleware
的白名单中移除,让中间件做统一的集群访问校验(修改 EnsureSelectedClusterMiddleware 白名单逻辑并确保它验证 clusterID
的访问权限)。
In `@pkg/plugins/modules/kubeconfig_export/frontend/management.json`:
- Around line 56-59: 前端将 token 作为 URL 查询参数拼接在下载链接上(参见 kubeconfigDownloadButton /
KubeconfigDownloadButton.tsx 中构造下载 URL 的逻辑),存在泄露风险;请改为使用 fetch 或 axios
请求并在请求头中添加 Authorization: Bearer <token>,以获取 blob 响应并通过创建对象 URL 或文件保存触发下载(不要把
token 放到 URLSearchParams);同时在后端的 jwt_claims.go 中移除或禁用从查询参数读取 token 的备选分支(删除或注释掉对
c.Query("token") 的兼容逻辑),确保仅从 Authorization 请求头验证。
In `@pkg/plugins/modules/kubeconfig_export/mgm/mgm.go`:
- Around line 93-125: GetKubeConfigByID currently returns sensitive
kubeconfig.content with only AuthMiddleware protection; restrict access by
adding PlatformAuthMiddleware (or an equivalent role check) to the /mgm route
handler or perform an explicit role/permission check inside GetKubeConfigByID
(e.g., verify caller is platform admin) before returning the content; update the
route registration or add a short guard in GetKubeConfigByID that calls
PlatformAuthMiddleware or checks the user role from context and return an
unauthorized error if the user is not an admin.
In `@ui/src/styles/global.scss`:
- Around line 12-14: 不要依赖构建期/开发期生成的类名(css-dev-only-do-not-override-mncuj7 和
_content_il4rv_44)作为稳定选择器;将样式从
main.ant-layout-content._content_il4rv_44.css-dev-only-do-not-override-mncuj7
提取为基于稳定类/语义类(例如给对应组件或容器添加 .app-main-content 或 .main-content)或只使用库的稳定类名(如
.ant-layout-content)加上你的自定义类,然后在对应组件模板/JSX 中为容器添加该稳定类以确保 min-height: 700px
生效。确保移除或不再引用生成的后缀类名并更新样式引用与组件标记一致。
---
Nitpick comments:
In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go`:
- Around line 200-202: 在将 exportedKubeConfig 写入响应时忽略了 Write 返回的错误;在调用
c.Writer.Write(exportedKubeConfig) 后捕获返回的 err 并记录下来(使用当前作用域内的日志对象,例如已有的
logger/klog/processLogger 或者函数内可用的日志变量),同时保留已发送的状态码;具体修改点:在调用
c.Writer.WriteHeader(200) 之后、c.Writer.Write(exportedKubeConfig)
的地方检查错误并调用日志记录函数记录包含上下文(如 "failed to write kubeconfig response" 和
exportedKubeConfig 长度或目标集群标识)的错误信息。
In `@pkg/plugins/modules/kubeconfig_export/lifecycle.go`:
- Around line 11-13: KubeconfigExportLifecycle 中声明的 cancelStart 字段未被使用:删除结构体中的
cancelStart 字段并同时移除未使用的 "context" 导入;参考 pkg/plugins/modules/demo/lifecycle.go
的模式,若未来需要后台任务再恢复并在 Start() 中创建 context.WithCancel 并在 Stop() 中调用
cancelStart(),否则保持结构体和导入精简。
In `@pkg/plugins/modules/kubeconfig_export/mgm/mgm.go`:
- Around line 148-150: The code reads a query parameter into an unused variable
("_ = c.Query(\"description\")"); remove that unused query read or, if the
description will be used later, replace the discard with a proper variable
(e.g., description := c.Query("description")) and add a TODO comment explaining
its planned use; locate the snippet where namespace := c.Query("namespace") and
role := c.Query("role") are set in mgm.go and modify the line handling the
"description" parameter accordingly.
- Around line 199-201: The response write currently ignores the error from
c.Writer.Write(exportedKubeConfig); update the code in mgm.go to capture the
returned (n, err) from c.Writer.Write, check err, and log any write failures
(including contextual info like requested resource or bytes written) using the
package's logger (or c.Logger/context logger) so write errors are not silently
dropped; ensure any logging call includes the error value and relevant
identifiers for debugging.
- Around line 78-91: GetClusterKubeconfig currently returns a "not implemented"
error; either remove/unregister the route or implement the handler: in
GetClusterKubeconfig validate clusterID (already done), query your cluster
store/DB for the kubeconfig by clusterID, handle not-found and DB errors by
calling amis.WriteJsonError with the error, and on success return the kubeconfig
payload using the project's JSON success helper (replace the current
amis.WriteJsonError("功能暂未实现")); if you intentionally leave it unimplemented add
a clear TODO comment above GetClusterKubeconfig explaining the planned
implementation and remove the route registration until implemented.
In `@ui/public/pages/plugins/kubeconfig_export/management.json`:
- Around line 1-63: The CRUD page config (the "crud" component with api
"get:/mgm/plugins/kubeconfig_export/templates") lacks pagination settings; add
pagination by specifying perPage (and optional perPageOptions), enable a
footerToolbar that includes "pagination" (and optionally "statistics"), and
ensure the API is queried with the expected pageField/perPageField if the
backend requires them; update the CRUD config keys (e.g., perPage,
perPageOptions, footerToolbar, pageField, perPageField) so the list supports
server-side pagination and better performance.
In `@ui/src/components/Amis/custom/KubeconfigDownloadButton.tsx`:
- Around line 13-36: The current handleDownload function triggers a download via
an anchor element so network errors (401/404/500) aren't caught by the
try/catch; change handleDownload to perform a fetch to the same endpoint
(including the token from localStorage either in headers or query params), check
response.ok, read the response as a Blob, create an object URL and
programmatically download it (using data.cluster for the filename and data.id
for the URL), revoke the object URL after use, and call message.success or
message.error based on the fetch/response outcome so real HTTP failures are
reported to the user.
- Around line 21-23: The current check if (params) is always true because params
is produced by URLSearchParams.toString() (e.g., "token="), so change the
condition to only append the query string when it's non-empty (e.g., params !==
'' or params.length > 0), or alternatively check the URLSearchParams instance
for actual keys before calling toString(); update the logic in the
KubeconfigDownloadButton where params and URLSearchParams.toString() are used to
ensure you only add `?${params}` when params contains at least one real
key-value pair.
In `@ui/src/styles/global.scss`:
- Around line 16-18: The global rule hiding `#translate` is too broad; restrict
its scope by removing or changing the global selector and instead place a scoped
rule like .<target-page>-container `#translate` { display: none; } or move the
rule into the target component's stylesheet so only the specific page container
(e.g. .conversation-container or `#chat-page`) affects `#translate`; update
references to the `#translate` selector accordingly and delete the broad global
`#translate { display: none; }` from global.scss.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2f1d004c-2dbc-4550-ade1-c810b0f407ce
⛔ Files ignored due to path filters (1)
k8m.logis excluded by!**/*.log
📒 Files selected for processing (17)
pkg/plugins/manager_api.gopkg/plugins/manager_router.gopkg/plugins/modules/kubeconfig_export/README.mdpkg/plugins/modules/kubeconfig_export/cluster/cluster.gopkg/plugins/modules/kubeconfig_export/frontend/management.jsonpkg/plugins/modules/kubeconfig_export/lifecycle.gopkg/plugins/modules/kubeconfig_export/metadata.gopkg/plugins/modules/kubeconfig_export/mgm/mgm.gopkg/plugins/modules/kubeconfig_export/route/cluster_api.gopkg/plugins/modules/kubeconfig_export/route/mgm_api.gopkg/plugins/modules/list.gopkg/plugins/modules/registrar/registrar.goui/public/pages/admin/plugins/list.jsonui/public/pages/plugins/kubeconfig_export/management.jsonui/src/components/Amis/custom/KubeconfigDownloadButton.tsxui/src/components/Amis/index.tsxui/src/styles/global.scss
| // GenerateRequest 生成 kubeconfig 的请求参数 | ||
| type GenerateRequest struct { | ||
| Namespace string `json:"namespace,omitempty"` // 限制的 namespace | ||
| Duration int `json:"duration,omitempty"` // 有效期(天) | ||
| Role string `json:"role,omitempty"` // 角色:admin, edit, view | ||
| Description string `json:"description,omitempty"` // 描述 | ||
| } |
There was a problem hiding this comment.
Duration 和 Role 字段已定义但未使用,可能误导用户
GenerateRequest 中的 Duration(有效期)和 Role(角色限制)字段暗示会生成具有时间限制和权限范围限制的 kubeconfig,但实际实现直接返回了原始的完整 kubeconfig,未做任何限制处理。
响应中虽然返回了 role 字段(第 83 行),但这只是原样返回请求参数,并未实际应用角色权限限制。这可能导致用户误以为导出的 kubeconfig 具有受限权限,造成安全隐患。
建议:
- 移除未实现的字段,或
- 添加 TODO 注释明确标注待实现,或
- 实际实现基于 Role/Duration 的 ServiceAccount + RBAC 绑定逻辑
Also applies to: 77-85
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go` around lines 14 -
20, GenerateRequest currently declares Duration and Role but they are not
enforced when handing back kubeconfigs; either remove these fields or mark them
TODO, or implement enforcement: in the handler that processes GenerateRequest
and returns the kubeconfig (the code that currently echoes role in the
response), create a ServiceAccount, create/apply a Role or ClusterRole and
corresponding RoleBinding/ClusterRoleBinding according to GenerateRequest.Role,
obtain a token for that ServiceAccount via TokenRequest (or create a secret)
with expiration based on GenerateRequest.Duration, build a kubeconfig that uses
that ServiceAccount token and return it (and ensure the response.role reflects
the actually applied role). Update the GenerateRequest struct or add TODO
comments if you choose not to implement enforcement.
| // 获取集群配置 | ||
| clusterConfig := service.ClusterService().GetClusterByID(clusterID) | ||
| if clusterConfig == nil { | ||
| amis.WriteJsonError(c, fmt.Errorf("集群不存在")) | ||
| return | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 验证 kubeconfig_export 插件路由是否配置了权限中间件
rg -n -A10 "kubeconfig.export|kubeconfig_export" --type=go -g '*route*'Repository: weibaohui/k8m
Length of output: 39
🏁 Script executed:
find . -type f -name "*.go" -path "*kubeconfig_export*" | head -20Repository: weibaohui/k8m
Length of output: 389
🏁 Script executed:
# Search for route/plugin registration patterns
rg -n "kubeconfig_export|kubeconfig.export" --type=go | head -40Repository: weibaohui/k8m
Length of output: 1872
🏁 Script executed:
# Look for middleware definitions and route registration in plugin system
rg -n "EnsureSelectedClusterMiddleware" --type=go | head -20Repository: weibaohui/k8m
Length of output: 325
🏁 Script executed:
# Search for plugin route registration mechanism
rg -n "plugin.*register|RegisterPlugin|register.*route" -i --type=go | grep -i plugin | head -20Repository: weibaohui/k8m
Length of output: 1741
🏁 Script executed:
cat -n pkg/plugins/modules/kubeconfig_export/route/cluster_api.goRepository: weibaohui/k8m
Length of output: 805
🏁 Script executed:
cat -n pkg/plugins/modules/kubeconfig_export/route/mgm_api.goRepository: weibaohui/k8m
Length of output: 1354
🏁 Script executed:
cat -n pkg/plugins/manager_router.goRepository: weibaohui/k8m
Length of output: 2803
🏁 Script executed:
cat -n main.go | grep -A 20 -B 5 "RegisterClusterRoutes\|EnsureSelectedClusterMiddleware"Repository: weibaohui/k8m
Length of output: 3393
🏁 Script executed:
cat -n pkg/middleware/cluster.goRepository: weibaohui/k8m
Length of output: 4171
🏁 Script executed:
# Check the mgm handlers to see if they validate cluster permissions
cat -n pkg/plugins/modules/kubeconfig_export/mgm/mgm.go | head -80Repository: weibaohui/k8m
Length of output: 2584
🏁 Script executed:
cat -n pkg/plugins/modules/kubeconfig_export/mgm/mgm.go | sed -n '78,130p'Repository: weibaohui/k8m
Length of output: 1836
🏁 Script executed:
# Check what permissions the handler validates
rg -n "GetClusterKubeconfig|GetClusterByID" pkg/plugins/modules/kubeconfig_export/mgm/mgm.go -A 20Repository: weibaohui/k8m
Length of output: 701
🏁 Script executed:
# Verify if service layer validates permissions
rg -n "GetClusterByID" pkg/service/ -A 10 | head -50Repository: weibaohui/k8m
Length of output: 2648
确保 /mgm 路由的 kubeconfig 导出功能有适当的授权检查
根据代码审查,EnsureSelectedClusterMiddleware 在第 54 行显式跳过了 /mgm/ 路由的集群验证。这意味着 /mgm/plugins/kubeconfig_export/kubeconfig/{id}/export 等管理路由不受集群访问权限中间件的保护。
GetKubeConfigByID 和 ExportKubeConfig 处理器直接通过 ID 从数据库检索 kubeconfig,但未执行任何授权检查来验证当前用户是否有权导出该 kubeconfig。由于 kubeconfig 包含集群管理员凭证,这可能导致任意认证用户都能导出系统中任意的 kubeconfig。
建议:
- 在 mgm 处理器中添加权限验证,确保用户只能导出其有权访问的 kubeconfig
- 或者从
/mgm路由的中间件白名单中移除此插件的路由,改为受集群权限中间件保护
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go` around lines 39 -
44, 当前 GetKubeConfigByID / ExportKubeConfig 直接按 ID 从 DB 读取 kubeconfig(通过
service.ClusterService().GetClusterByID)但没有任何权限校验,且
EnsureSelectedClusterMiddleware 在 mgm 路由上被白名单跳过,导致任意登录用户可导出任意 kubeconfig;请在这些
mgm 处理器中加入授权检查(例如在 GetKubeConfigByID 和 ExportKubeConfig 中使用当前用户信息调用一个权限方法如
ClusterService().CanAccessCluster(user, clusterID) 或检查用户所属项目/租户与 clusterID
关联),在无权限时返回 403 并停止导出;或者把该插件路由从 EnsureSelectedClusterMiddleware
的白名单中移除,让中间件做统一的集群访问校验(修改 EnsureSelectedClusterMiddleware 白名单逻辑并确保它验证 clusterID
的访问权限)。
| // sanitizeFilename 清理文件名,移除可能破坏响应头的字符 | ||
| func sanitizeFilename(input string) string { | ||
| // 移除控制字符(ASCII < 0x20,不包括 CR LF)和控制字符 DEL | ||
| var result strings.Builder | ||
| for _, r := range input { | ||
| // 允许字母、数字、中文、空格、连字符、下划线、点 | ||
| // 移除引号、分号、反斜杠等可能导致头注入的字符 | ||
| if r >= 32 && r < 127 { | ||
| // ASCII 可打印字符 | ||
| if r == '"' || r == ';' || r == '\\' || r == '/' { | ||
| continue // 移除特殊字符 | ||
| } | ||
| result.WriteRune(r) | ||
| } else if r > 127 { | ||
| // 允许中文字符(Unicode 大于 127) | ||
| result.WriteRune(r) | ||
| } | ||
| } | ||
| resultStr := result.String() | ||
| if resultStr == "" { | ||
| resultStr = "kubeconfig" | ||
| } | ||
| return resultStr | ||
| } |
There was a problem hiding this comment.
注释与实现不一致
第 101 行注释写着"不包括 CR LF",但代码实际上会过滤所有 r < 32 的字符(包括 CR=13 和 LF=10)。代码实现是正确的,建议修正注释以避免混淆。
📝 建议修正注释
// sanitizeFilename 清理文件名,移除可能破坏响应头的字符
func sanitizeFilename(input string) string {
- // 移除控制字符(ASCII < 0x20,不包括 CR LF)和控制字符 DEL
+ // 移除控制字符(ASCII < 0x20)
var result strings.Builder📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // sanitizeFilename 清理文件名,移除可能破坏响应头的字符 | |
| func sanitizeFilename(input string) string { | |
| // 移除控制字符(ASCII < 0x20,不包括 CR LF)和控制字符 DEL | |
| var result strings.Builder | |
| for _, r := range input { | |
| // 允许字母、数字、中文、空格、连字符、下划线、点 | |
| // 移除引号、分号、反斜杠等可能导致头注入的字符 | |
| if r >= 32 && r < 127 { | |
| // ASCII 可打印字符 | |
| if r == '"' || r == ';' || r == '\\' || r == '/' { | |
| continue // 移除特殊字符 | |
| } | |
| result.WriteRune(r) | |
| } else if r > 127 { | |
| // 允许中文字符(Unicode 大于 127) | |
| result.WriteRune(r) | |
| } | |
| } | |
| resultStr := result.String() | |
| if resultStr == "" { | |
| resultStr = "kubeconfig" | |
| } | |
| return resultStr | |
| } | |
| // sanitizeFilename 清理文件名,移除可能破坏响应头的字符 | |
| func sanitizeFilename(input string) string { | |
| // 移除控制字符(ASCII < 0x20) | |
| var result strings.Builder | |
| for _, r := range input { | |
| // 允许字母、数字、中文、空格、连字符、下划线、点 | |
| // 移除引号、分号、反斜杠等可能导致头注入的字符 | |
| if r >= 32 && r < 127 { | |
| // ASCII 可打印字符 | |
| if r == '"' || r == ';' || r == '\\' || r == '/' { | |
| continue // 移除特殊字符 | |
| } | |
| result.WriteRune(r) | |
| } else if r > 127 { | |
| // 允许中文字符(Unicode 大于 127) | |
| result.WriteRune(r) | |
| } | |
| } | |
| resultStr := result.String() | |
| if resultStr == "" { | |
| resultStr = "kubeconfig" | |
| } | |
| return resultStr | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go` around lines 99 -
122, The comment above sanitizeFilename is inaccurate: it says "不包括 CR LF" but
the implementation removes all control characters with code < 32 (which includes
CR and LF); update the comment for sanitizeFilename to accurately state that
control characters (ASCII < 0x20, including CR and LF) and DEL are removed, or
otherwise adjust the wording to reflect that CR/LF are also filtered by the
function.
| // 获取请求参数 | ||
| var req ExportRequest | ||
| if err := c.ShouldBindJSON(&req); err != nil { | ||
| // 如果解析失败,使用默认值 | ||
| req = ExportRequest{ | ||
| Format: "yaml", | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 验证 Export 端点的路由注册方式
rg -n "Export" --type=go -C3 -g '*route*' -g '*api*'Repository: weibaohui/k8m
Length of output: 2413
🏁 Script executed:
#!/bin/bash
# Find and examine the ExportRequest struct definition
rg -n "type ExportRequest" --type=go -A 10
# Also check the full Export function
rg -n "func.*Export" --type=go pkg/plugins/modules/kubeconfig_export/cluster/cluster.go -A 15Repository: weibaohui/k8m
Length of output: 1566
🏁 Script executed:
#!/bin/bash
# Get the full Export function implementation
sed -n '125,200p' pkg/plugins/modules/kubeconfig_export/cluster/cluster.goRepository: weibaohui/k8m
Length of output: 1831
GET 请求使用 JSON Body 绑定导致功能损坏
端点注册为 GET 方法(cluster_api.go 第 17 行),但代码尝试通过 ShouldBindJSON 从请求体读取参数。GET 请求通常不携带请求体,这会导致绑定失败。虽然代码回退到默认值,但这导致 Namespace、Role、Duration 和 Description 参数完全丧失。这些参数在函数逻辑中被使用(用于设置 kubeconfig context 和生成文件名),因此无法接收用户输入会破坏核心功能。
建议方案:
- 将路由改为 POST 方法,或
- 使用 Query 参数绑定替代 JSON Body(推荐 GET 使用 Query 参数)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go` around lines 133 -
140, The handler currently tries to parse GET request parameters using
ShouldBindJSON which fails for GET; update the binding to use query parameters
instead by replacing ShouldBindJSON(&req) with query-binding (e.g., use
ShouldBindQuery(&req) or manually read
c.Query("Namespace")/c.Query("Role")/c.Query("Duration")/c.Query("Description")/c.Query("Format"))
so ExportRequest fields are populated for GET; alternatively if you prefer body
JSON keep the JSON binding but change the route in cluster_api.go to POST
(adjust the route registration at the site referenced in cluster_api.go line 17)
so the handler and ExportRequest remain consistent.
| // 查询数据库中的 kubeconfig | ||
| // 注意:这个函数暂时未实现,因为需要从数据库查询 kubeconfig | ||
| // 未来可以根据实际需求实现 | ||
| amis.WriteJsonError(c, fmt.Errorf("功能暂未实现")) | ||
|
|
||
| klog.V(6).Infof("请求导出 kubeconfig ID %s", idStr) | ||
| } No newline at end of file |
There was a problem hiding this comment.
错误响应后缺少 return 语句
第 227 行返回错误响应后,代码继续执行到第 229 行记录日志。这会导致即使功能未实现返回了错误,仍会记录"请求导出 kubeconfig"的日志,逻辑不一致。
🐛 添加 return 语句
// 查询数据库中的 kubeconfig
// 注意:这个函数暂时未实现,因为需要从数据库查询 kubeconfig
// 未来可以根据实际需求实现
amis.WriteJsonError(c, fmt.Errorf("功能暂未实现"))
+ return
- klog.V(6).Infof("请求导出 kubeconfig ID %s", idStr)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 查询数据库中的 kubeconfig | |
| // 注意:这个函数暂时未实现,因为需要从数据库查询 kubeconfig | |
| // 未来可以根据实际需求实现 | |
| amis.WriteJsonError(c, fmt.Errorf("功能暂未实现")) | |
| klog.V(6).Infof("请求导出 kubeconfig ID %s", idStr) | |
| } | |
| // 查询数据库中的 kubeconfig | |
| // 注意:这个函数暂时未实现,因为需要从数据库查询 kubeconfig | |
| // 未来可以根据实际需求实现 | |
| amis.WriteJsonError(c, fmt.Errorf("功能暂未实现")) | |
| return | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/plugins/modules/kubeconfig_export/cluster/cluster.go` around lines 224 -
230, 在处理导出 kubeconfig 的请求时,amis.WriteJsonError(c, fmt.Errorf("功能暂未实现"))
发送错误响应后缺少 return,导致后续仍会执行 klog.V(6).Infof("请求导出 kubeconfig ID %s", idStr)
并记录不一致的日志;在该处理函数中(定位到调用 amis.WriteJsonError 的位置),在该调用语句之后立即添加
return,确保在发送错误响应后函数立即返回,不再执行后续的 klog.V(6).Infof 或其他逻辑。
| "itemActions": [ | ||
| { | ||
| "type": "kubeconfigDownloadButton" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 验证下载按钮是否通过 query 参数拼接 token
fd -i "KubeconfigDownloadButton.tsx" | xargs -I{} rg -n -C2 'localStorage\.getItem\(|URLSearchParams|token|/kubeconfig/.*/export' {}Repository: weibaohui/k8m
Length of output: 400
🏁 Script executed:
find . -type f -name "*.go" -o -name "*.ts" -o -name "*.js" | xargs rg -l "kubeconfig.*export" | head -20Repository: weibaohui/k8m
Length of output: 354
🏁 Script executed:
# Find the backend handler for kubeconfig export endpoint
fd -i "*.go" | xargs rg -n "kubeconfig_export|/kubeconfig/.*/export" -A 5 -B 2Repository: weibaohui/k8m
Length of output: 25304
🏁 Script executed:
# Check if token is read from query parameters in the backend
fd -i "*.go" | xargs rg -n "\.Query\(.*token\)|r\.URL\.Query" -A 3 -B 2 | head -50Repository: weibaohui/k8m
Length of output: 340
🏁 Script executed:
find pkg/plugins/modules/kubeconfig_export -name "*.go" -type fRepository: weibaohui/k8m
Length of output: 377
🏁 Script executed:
# Find the ExportKubeConfig function definition
rg -n "ExportKubeConfig\|func.*Export" pkg/plugins/modules/kubeconfig_export/ -A 20Repository: weibaohui/k8m
Length of output: 39
🏁 Script executed:
# Check how query parameters are used in the backend
rg -n "Query\(|token|Authorization" pkg/plugins/modules/kubeconfig_export/ -B 2 -A 2Repository: weibaohui/k8m
Length of output: 582
🏁 Script executed:
sed -n '130,180p' pkg/plugins/modules/kubeconfig_export/mgm/mgm.goRepository: weibaohui/k8m
Length of output: 1306
🏁 Script executed:
# Get the entire ExportKubeConfig function
rg -n "func.*ExportKubeConfig" pkg/plugins/modules/kubeconfig_export/mgm/mgm.go -A 60Repository: weibaohui/k8m
Length of output: 1810
🏁 Script executed:
# Check if token parameter is explicitly read anywhere
rg -n "Query.*token\|token.*Query" pkg/plugins/modules/kubeconfig_export/Repository: weibaohui/k8m
Length of output: 39
🏁 Script executed:
# See the rest of the ExportKubeConfig function to see how the response is generated
sed -n '180,220p' pkg/plugins/modules/kubeconfig_export/mgm/mgm.goRepository: weibaohui/k8m
Length of output: 697
🏁 Script executed:
# Check if there's any middleware or auth that reads token from query params
rg -n "token.*query|Query.*token|query.*token" pkg/ -i -B 2 -A 2 | head -50Repository: weibaohui/k8m
Length of output: 354
🏁 Script executed:
# Check the complete mgm.go file to see the full ExportKubeConfig function
wc -l pkg/plugins/modules/kubeconfig_export/mgm/mgm.go && tail -50 pkg/plugins/modules/kubeconfig_export/mgm/mgm.goRepository: weibaohui/k8m
Length of output: 1360
修复 Token 泄漏风险:将认证信息从 URL 查询参数改为 Authorization 请求头。
验证确认存在安全隐患:前端代码将认证 token 通过 URLSearchParams 拼接到下载 URL(ui/src/components/Amis/custom/KubeconfigDownloadButton.tsx 第 15-22 行),后端认证中间件(pkg/comm/utils/jwt_claims.go 第 18 行)也显式支持从查询参数读取 token 作为备选认证方式。这导致 token 暴露在:
- 浏览器地址栏与历史记录
- 服务器和代理的访问日志
- HTTP Referer 请求头
建议改为:
- 前端:使用 fetch/axios + Authorization 请求头 + Blob 响应处理(不通过 URL 下载)
- 后端:删除 jwt_claims.go 中的
c.Query("token")查询参数兼容逻辑
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/plugins/modules/kubeconfig_export/frontend/management.json` around lines
56 - 59, 前端将 token 作为 URL 查询参数拼接在下载链接上(参见 kubeconfigDownloadButton /
KubeconfigDownloadButton.tsx 中构造下载 URL 的逻辑),存在泄露风险;请改为使用 fetch 或 axios
请求并在请求头中添加 Authorization: Bearer <token>,以获取 blob 响应并通过创建对象 URL 或文件保存触发下载(不要把
token 放到 URLSearchParams);同时在后端的 jwt_claims.go 中移除或禁用从查询参数读取 token 的备选分支(删除或注释掉对
c.Query("token") 的兼容逻辑),确保仅从 Authorization 请求头验证。
| // GetKubeConfigByID 根据 ID 获取 kubeconfig(用于导出) | ||
| func GetKubeConfigByID(c *response.Context) { | ||
| idStr := c.Param("id") | ||
| if idStr == "" { | ||
| amis.WriteJsonError(c, fmt.Errorf("kubeconfig ID不能为空")) | ||
| return | ||
| } | ||
|
|
||
| klog.V(6).Infof("获取 kubeconfig ID %s", idStr) | ||
|
|
||
| params := dao.BuildParams(c) | ||
| kc := &models.KubeConfig{} | ||
| kubeConfig, err := kc.GetOne(params, func(db *gorm.DB) *gorm.DB { | ||
| return db.Where("id = ?", idStr) | ||
| }) | ||
| if err != nil || kubeConfig == nil { | ||
| amis.WriteJsonError(c, fmt.Errorf("kubeconfig 不存在")) | ||
| return | ||
| } | ||
|
|
||
| // 返回完整的 kubeconfig 内容 | ||
| amis.WriteJsonData(c, map[string]any{ | ||
| "id": kubeConfig.ID, | ||
| "server": kubeConfig.Server, | ||
| "user": kubeConfig.User, | ||
| "cluster": kubeConfig.Cluster, | ||
| "namespace": kubeConfig.Namespace, | ||
| "display_name": kubeConfig.DisplayName, | ||
| "content": kubeConfig.Content, | ||
| }) | ||
|
|
||
| klog.V(6).Infof("成功获取 kubeconfig ID %s 的内容", idStr) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 检查 mgm 路由是否有认证中间件保护
rg -n -A5 -B5 'ManagementRouter|RegisterManagementRoutes' --type=goRepository: weibaohui/k8m
Length of output: 12841
🏁 Script executed:
# 查找middleware相关的认证实现
rg -n 'PlatformAuthMiddleware|LoginRequired|middleware\.' --type=go -B2 -A2 | head -100Repository: weibaohui/k8m
Length of output: 1128
🏁 Script executed:
# 查看main.go中/mgm路由的完整上下文
cat -n main.go | sed -n '240,270p'Repository: weibaohui/k8m
Length of output: 1233
🏁 Script executed:
# 检查是否有全局中间件应用到所有路由
rg -n 'Use\(|Use\s' --type=go pkg/ | head -50Repository: weibaohui/k8m
Length of output: 320
🏁 Script executed:
# 查找GetKubeConfigByID是否有特殊的认证检查
rg -n 'GetKubeConfigByID' --type=go -B5 -A15Repository: weibaohui/k8m
Length of output: 3791
🏁 Script executed:
# 查看AuthMiddleware的完整实现
cat -n pkg/middleware/auth.go | head -100Repository: weibaohui/k8m
Length of output: 3237
🏁 Script executed:
# 查看response.Context是否有权限检查方法
rg -n 'func.*Context\).*' pkg/response --type=go | head -30Repository: weibaohui/k8m
Length of output: 1785
🏁 Script executed:
# 检查dao.BuildParams是否涉及权限检查
rg -n 'BuildParams' --type=go -B3 -A10 | head -50Repository: weibaohui/k8m
Length of output: 3240
/mgm 路由缺少角色级别的访问控制
该端点返回包含敏感凭据的 content 字段,但仅通过 AuthMiddleware(只检查登录状态)保护。与 /admin 路由不同,/admin 路由使用 PlatformAuthMiddleware 进行管理员角色验证,而 /mgm 路由的访问控制不足。任何已登录用户都可以访问并导出包含完整 Kubernetes 凭据的配置文件。建议为此端点添加 PlatformAuthMiddleware 或实现更细粒度的权限检查,以限制只有平台管理员可以访问敏感的 kubeconfig 凭据。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pkg/plugins/modules/kubeconfig_export/mgm/mgm.go` around lines 93 - 125,
GetKubeConfigByID currently returns sensitive kubeconfig.content with only
AuthMiddleware protection; restrict access by adding PlatformAuthMiddleware (or
an equivalent role check) to the /mgm route handler or perform an explicit
role/permission check inside GetKubeConfigByID (e.g., verify caller is platform
admin) before returning the content; update the route registration or add a
short guard in GetKubeConfigByID that calls PlatformAuthMiddleware or checks the
user role from context and return an unauthorized error if the user is not an
admin.
| main.ant-layout-content._content_il4rv_44.css-dev-only-do-not-override-mncuj7{ | ||
| min-height: 700px !important; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 验证是否仍在使用脆弱的生成 class 作为全局样式选择器
rg -n --type=scss 'css-dev-only-do-not-override|_[a-z0-9]+_[0-9]+' ui/src/styles/global.scssRepository: weibaohui/k8m
Length of output: 85
🏁 Script executed:
cat -n ui/src/styles/global.scss | head -30Repository: weibaohui/k8m
Length of output: 786
🏁 Script executed:
rg -n 'css-dev-only-do-not-override|_[a-z0-9]+_[0-9]+' ui/src/styles/global.scssRepository: weibaohui/k8m
Length of output: 138
🏁 Script executed:
rg -n 'min-height.*700px' ui/src/styles/global.scssRepository: weibaohui/k8m
Length of output: 91
避免依赖构建期/开发期生成的 class 选择器。
这里把 css-dev-only-do-not-override-mncuj7 和 _content_il4rv_44 当作稳定选择器使用,升级依赖或调整构建配置后很容易失效,导致页面高度规则丢失。
♻️ 建议修复
-main.ant-layout-content._content_il4rv_44.css-dev-only-do-not-override-mncuj7{
- min-height: 700px !important;
-}
+/* 使用稳定选择器(建议再配合页面级容器类做作用域限制) */
+.ant-layout-content {
+ min-height: 700px;
+}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ui/src/styles/global.scss` around lines 12 - 14,
不要依赖构建期/开发期生成的类名(css-dev-only-do-not-override-mncuj7 和
_content_il4rv_44)作为稳定选择器;将样式从
main.ant-layout-content._content_il4rv_44.css-dev-only-do-not-override-mncuj7
提取为基于稳定类/语义类(例如给对应组件或容器添加 .app-main-content 或 .main-content)或只使用库的稳定类名(如
.ant-layout-content)加上你的自定义类,然后在对应组件模板/JSX 中为容器添加该稳定类以确保 min-height: 700px
生效。确保移除或不再引用生成的后缀类名并更新样式引用与组件标记一致。
Resolve conflicts using PR branch version (POST body for spec)
No description provided.