Skip to content

Feature/kubeconfig export#458

Merged
weibaohui merged 19 commits intomainfrom
feature/kubeconfig-export
Mar 28, 2026
Merged

Feature/kubeconfig export#458
weibaohui merged 19 commits intomainfrom
feature/kubeconfig-export

Conversation

@weibaohui
Copy link
Copy Markdown
Owner

No description provided.

新增功能:
- 创建 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元素
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Summary by CodeRabbit

发布说明

  • 新功能

    • 新增 Kubeconfig 导出插件,支持按集群生成 kubeconfig,支持命名空间限制,可导出为 YAML 文件
    • 插件管理列表新增分页功能,默认每页显示 10 项
    • 新增 Kubeconfig 下载按钮,支持在管理界面直接导出集群配置文件
  • 界面优化

    • 改进插件定时任务管理交互方式
    • 优化全局页面滚动和布局样式

Walkthrough

本变更扩展了插件系统的核心管理功能,修改了cron规范的索引化管理和列表分页,并新增完整的kubeconfig导出插件实现,包括后端API、生命周期管理、前端UI和路由注册。

Changes

Cohort / File(s) Summary
插件管理API核心改动
pkg/plugins/manager_api.go
修改cron规范管理:从URL编码spec改为使用index索引,新增slices.Contains验证;添加ListPlugins分页支持,通过dao.BuildParams处理分页参数;扩展CronItemVO新增Index字段;更新RunPluginCronOnce从POST体读spec并验证存在性。
插件管理路由
pkg/plugins/manager_router.go
扩展RegisterManagementRoutes中的status条件检查,从仅StatusRunning改为StatusEnabled/StatusRunning/StatusStopped之一。
Kubeconfig导出插件核心实现
pkg/plugins/modules/kubeconfig_export/lifecycle.go, pkg/plugins/modules/kubeconfig_export/metadata.go, pkg/plugins/modules/kubeconfig_export/mgm/mgm.go, pkg/plugins/modules/kubeconfig_export/cluster/cluster.go
新增kubeconfig导出插件的完整实现:生命周期管理、元数据定义、模板管理API(列表、获取、导出)、集群API(生成、导出);包含文件名清理、命名空间覆盖、kubeconfig解析和序列化等功能。
Kubeconfig导出路由与注册
pkg/plugins/modules/kubeconfig_export/route/mgm_api.go, pkg/plugins/modules/kubeconfig_export/route/cluster_api.go, pkg/plugins/modules/list.go, pkg/plugins/modules/registrar/registrar.go
添加kubeconfig导出插件的路由注册(管理和集群端点)、插件常量声明、以及在registrar中注册插件元数据。
Kubeconfig导出文档
pkg/plugins/modules/kubeconfig_export/README.md
新增插件功能文档,说明kubeconfig生成、导出、命名空间限制等功能及API端点。
插件管理UI重构
ui/public/pages/admin/plugins/list.json
重构插件列表页面:从perPage: 1000改为perPage: 10启用分页;更新API调用为get:/admin/plugin/list?page=\${page}&perPage=\${perPage};修改cron管理路由使用index替代base64编码spec;更新执行一次操作为显式方法+数据形式。
Kubeconfig管理UI页面
ui/public/pages/plugins/kubeconfig_export/management.json, ui/public/pages/plugins/kubeconfig_export/management.json (frontend/mgm)
新增kubeconfig管理页面,配置CRUD组件、表列(模板ID、集群、服务器等)、下载按钮、工具栏操作。
前端自定义组件
ui/src/components/Amis/custom/KubeconfigDownloadButton.tsx, ui/src/components/Amis/index.tsx
新增导出Kubeconfig按钮组件,支持文件下载和错误提示;在AMIS组件注册中集成为自定义渲染器。
样式更新
ui/src/styles/global.scss
修改全局样式:启用HTML/body滚动(overflow: auto);添加主内容区最小高度约束(min-height: 700px !important);隐藏translate元素。

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 在代码的花园里欢快跳跃

kubeconfig导出插件绽放新花,
索引替代编码更清爽,
分页列表优雅滚动,
下载按钮一键导出,
文件安全名字被制造! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/kubeconfig-export
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feature/kubeconfig-export

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)不会被捕获,用户会看到"下载中"的成功消息,但实际下载可能失败。

如果需要更可靠的错误处理,建议改用 fetch API 并配合 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 组件没有配置分页参数(如 perPagefooterToolbar),当模板数量较多时可能影响性能和用户体验。建议参考 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

📥 Commits

Reviewing files that changed from the base of the PR and between a50d9c9 and 5e5df33.

⛔ Files ignored due to path filters (1)
  • k8m.log is excluded by !**/*.log
📒 Files selected for processing (17)
  • pkg/plugins/manager_api.go
  • pkg/plugins/manager_router.go
  • pkg/plugins/modules/kubeconfig_export/README.md
  • pkg/plugins/modules/kubeconfig_export/cluster/cluster.go
  • pkg/plugins/modules/kubeconfig_export/frontend/management.json
  • pkg/plugins/modules/kubeconfig_export/lifecycle.go
  • pkg/plugins/modules/kubeconfig_export/metadata.go
  • pkg/plugins/modules/kubeconfig_export/mgm/mgm.go
  • pkg/plugins/modules/kubeconfig_export/route/cluster_api.go
  • pkg/plugins/modules/kubeconfig_export/route/mgm_api.go
  • pkg/plugins/modules/list.go
  • pkg/plugins/modules/registrar/registrar.go
  • ui/public/pages/admin/plugins/list.json
  • ui/public/pages/plugins/kubeconfig_export/management.json
  • ui/src/components/Amis/custom/KubeconfigDownloadButton.tsx
  • ui/src/components/Amis/index.tsx
  • ui/src/styles/global.scss

Comment on lines +14 to +20
// 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"` // 描述
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

DurationRole 字段已定义但未使用,可能误导用户

GenerateRequest 中的 Duration(有效期)和 Role(角色限制)字段暗示会生成具有时间限制和权限范围限制的 kubeconfig,但实际实现直接返回了原始的完整 kubeconfig,未做任何限制处理。

响应中虽然返回了 role 字段(第 83 行),但这只是原样返回请求参数,并未实际应用角色权限限制。这可能导致用户误以为导出的 kubeconfig 具有受限权限,造成安全隐患。

建议:

  1. 移除未实现的字段,或
  2. 添加 TODO 注释明确标注待实现,或
  3. 实际实现基于 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.

Comment on lines +39 to +44
// 获取集群配置
clusterConfig := service.ClusterService().GetClusterByID(clusterID)
if clusterConfig == nil {
amis.WriteJsonError(c, fmt.Errorf("集群不存在"))
return
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -20

Repository: weibaohui/k8m

Length of output: 389


🏁 Script executed:

# Search for route/plugin registration patterns
rg -n "kubeconfig_export|kubeconfig.export" --type=go | head -40

Repository: weibaohui/k8m

Length of output: 1872


🏁 Script executed:

# Look for middleware definitions and route registration in plugin system
rg -n "EnsureSelectedClusterMiddleware" --type=go | head -20

Repository: 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 -20

Repository: weibaohui/k8m

Length of output: 1741


🏁 Script executed:

cat -n pkg/plugins/modules/kubeconfig_export/route/cluster_api.go

Repository: weibaohui/k8m

Length of output: 805


🏁 Script executed:

cat -n pkg/plugins/modules/kubeconfig_export/route/mgm_api.go

Repository: weibaohui/k8m

Length of output: 1354


🏁 Script executed:

cat -n pkg/plugins/manager_router.go

Repository: 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.go

Repository: 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 -80

Repository: 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 20

Repository: weibaohui/k8m

Length of output: 701


🏁 Script executed:

# Verify if service layer validates permissions
rg -n "GetClusterByID" pkg/service/ -A 10 | head -50

Repository: weibaohui/k8m

Length of output: 2648


确保 /mgm 路由的 kubeconfig 导出功能有适当的授权检查

根据代码审查,EnsureSelectedClusterMiddleware 在第 54 行显式跳过了 /mgm/ 路由的集群验证。这意味着 /mgm/plugins/kubeconfig_export/kubeconfig/{id}/export 等管理路由不受集群访问权限中间件的保护。

GetKubeConfigByIDExportKubeConfig 处理器直接通过 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
的访问权限)。

Comment on lines +99 to +122
// 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
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

注释与实现不一致

第 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.

Suggested change
// 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.

Comment on lines +133 to +140
// 获取请求参数
var req ExportRequest
if err := c.ShouldBindJSON(&req); err != nil {
// 如果解析失败,使用默认值
req = ExportRequest{
Format: "yaml",
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 15

Repository: 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.go

Repository: weibaohui/k8m

Length of output: 1831


GET 请求使用 JSON Body 绑定导致功能损坏

端点注册为 GET 方法(cluster_api.go 第 17 行),但代码尝试通过 ShouldBindJSON 从请求体读取参数。GET 请求通常不携带请求体,这会导致绑定失败。虽然代码回退到默认值,但这导致 NamespaceRoleDurationDescription 参数完全丧失。这些参数在函数逻辑中被使用(用于设置 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.

Comment on lines +224 to +230
// 查询数据库中的 kubeconfig
// 注意:这个函数暂时未实现,因为需要从数据库查询 kubeconfig
// 未来可以根据实际需求实现
amis.WriteJsonError(c, fmt.Errorf("功能暂未实现"))

klog.V(6).Infof("请求导出 kubeconfig ID %s", idStr)
} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

错误响应后缺少 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.

Suggested change
// 查询数据库中的 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 或其他逻辑。

Comment on lines +56 to +59
"itemActions": [
{
"type": "kubeconfigDownloadButton"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -20

Repository: 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 2

Repository: 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 -50

Repository: weibaohui/k8m

Length of output: 340


🏁 Script executed:

find pkg/plugins/modules/kubeconfig_export -name "*.go" -type f

Repository: weibaohui/k8m

Length of output: 377


🏁 Script executed:

# Find the ExportKubeConfig function definition
rg -n "ExportKubeConfig\|func.*Export" pkg/plugins/modules/kubeconfig_export/ -A 20

Repository: 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 2

Repository: weibaohui/k8m

Length of output: 582


🏁 Script executed:

sed -n '130,180p' pkg/plugins/modules/kubeconfig_export/mgm/mgm.go

Repository: 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 60

Repository: 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.go

Repository: 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 -50

Repository: 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.go

Repository: 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 请求头

建议改为:

  1. 前端:使用 fetch/axios + Authorization 请求头 + Blob 响应处理(不通过 URL 下载)
  2. 后端:删除 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 请求头验证。

Comment on lines +93 to +125
// 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)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查 mgm 路由是否有认证中间件保护
rg -n -A5 -B5 'ManagementRouter|RegisterManagementRoutes' --type=go

Repository: weibaohui/k8m

Length of output: 12841


🏁 Script executed:

# 查找middleware相关的认证实现
rg -n 'PlatformAuthMiddleware|LoginRequired|middleware\.' --type=go -B2 -A2 | head -100

Repository: 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 -50

Repository: weibaohui/k8m

Length of output: 320


🏁 Script executed:

# 查找GetKubeConfigByID是否有特殊的认证检查
rg -n 'GetKubeConfigByID' --type=go -B5 -A15

Repository: weibaohui/k8m

Length of output: 3791


🏁 Script executed:

# 查看AuthMiddleware的完整实现
cat -n pkg/middleware/auth.go | head -100

Repository: weibaohui/k8m

Length of output: 3237


🏁 Script executed:

# 查看response.Context是否有权限检查方法
rg -n 'func.*Context\).*' pkg/response --type=go | head -30

Repository: weibaohui/k8m

Length of output: 1785


🏁 Script executed:

# 检查dao.BuildParams是否涉及权限检查
rg -n 'BuildParams' --type=go -B3 -A10 | head -50

Repository: 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.

Comment on lines +12 to +14
main.ant-layout-content._content_il4rv_44.css-dev-only-do-not-override-mncuj7{
min-height: 700px !important;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.scss

Repository: weibaohui/k8m

Length of output: 85


🏁 Script executed:

cat -n ui/src/styles/global.scss | head -30

Repository: 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.scss

Repository: weibaohui/k8m

Length of output: 138


🏁 Script executed:

rg -n 'min-height.*700px' ui/src/styles/global.scss

Repository: 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)
@weibaohui weibaohui merged commit 0f19a1f into main Mar 28, 2026
6 checks passed
@weibaohui weibaohui deleted the feature/kubeconfig-export branch March 28, 2026 05:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant