Jenkins授权卡?试试我的方法

✍ 道路千万条,安全第一条。操作不规范,运维两行泪。
使用过 Jenkins 的应该都用过 Role Strategy plugin 来管理用户/组的权限管理,不是说它有多好用,而是没太多选择。
但是,不知道有没有发现一个问题:当用户的量级上去之后,打开授权界面经常会把浏览器卡死导致无法操作,为此我想是不是可以直接通过接口去授权,这样就不必打开那么卡的页面了。
通过 https://github.com/jenkinsci/role-strategy-plugin 学习,发现其有 REST API,提供一部分接口功能。
8ca930960a30b80f87512c5784aaeadd MD5
8ca930960a30b80f87512c5784aaeadd MD5
为此,我先写了一个 role-srategy-sdk 的SDK,地址是:https://github.com/joker-bai/go-role-strategy-sdk。该 SDK 基本涵盖了文档中的所有接口。
权限模板管理 – 创建、删除和查询权限模板
角色管理 – 支持全局角色、项目角色和节点角色的完整生命周期管理
用户/组分配 – 灵活的用户和组角色分配功能
查询功能 – 获取角色信息、分配情况和匹配的作业/代理
多角色类型 – 支持 GlobalRole、ProjectRole 和 SlaveRole
SDK的使用

安装
go get github.com/joker-bai/go-role-strategy-sdk
初始化客户端
package main

import (
“fmt”
“log”

rolestrategy "github.com/joker-bai/go-role-strategy-sdk"

)

func main() {
// 创建客户端
client := rolestrategy.NewClient(
“https://your-jenkins-url”,
“your-username”,
“your-api-token”,
)
}
权限模板管理
// 添加权限模板
err := client.AddTemplate(
“developer”,
“hudson.model.Item.Read,hudson.model.Item.Build”,
true, // overwrite
)
if err != nil {
log.Fatal(“AddTemplate:”, err)
}

// 获取权限模板
template, err := client.GetTemplate(“developer”)
if err != nil {
log.Fatal(“GetTemplate:”, err)
}
fmt.Printf(“Template: %+v\n”, template)

// 删除权限模板
err = client.RemoveTemplates([]string{“developer”}, false)
if err != nil {
log.Fatal(“RemoveTemplates:”, err)
}
角色管理
// 添加全局角色
err := client.AddRole(
rolestrategy.GlobalRole,
“dev-lead”,
“hudson.model.Hudson.Administer”,
true, // overwrite
“”, // pattern (项目角色使用)
“”, // template
)
if err != nil {
log.Fatal(“AddRole:”, err)
}

// 添加项目角色(带模式匹配)
err = client.AddRole(
rolestrategy.ProjectRole,
“project-dev”,
“hudson.model.Item.Read,hudson.model.Item.Build”,
true,
“dev-.*”, // 匹配以 dev- 开头的项目
“”,
)

// 获取角色信息
role, err := client.GetRole(rolestrategy.GlobalRole, “dev-lead”)
if err != nil {
log.Fatal(“GetRole:”, err)
}
fmt.Printf(“Role: %+v\n”, role)

// 删除角色
err = client.RemoveRoles(rolestrategy.GlobalRole, []string{“dev-lead”})
if err != nil {
log.Fatal(“RemoveRoles:”, err)
}
用户和组分配
// 分配用户角色
err := client.AssignUserRole(rolestrategy.GlobalRole, “dev-lead”, “alice”)
if err != nil {
log.Fatal(“AssignUserRole:”, err)
}

// 分配组角色
err = client.AssignGroupRole(rolestrategy.ProjectRole, “project-dev”, “developers”)
if err != nil {
log.Fatal(“AssignGroupRole:”, err)
}

// 取消用户角色分配
err = client.UnassignUserRole(rolestrategy.GlobalRole, “dev-lead”, “alice”)
if err != nil {
log.Fatal(“UnassignUserRole:”, err)
}

// 取消组角色分配
err = client.UnassignGroupRole(rolestrategy.ProjectRole, “project-dev”, “developers”)
if err != nil {
log.Fatal(“UnassignGroupRole:”, err)
}
查询功能
// 获取所有角色
allRoles, err := client.GetAllRoles(rolestrategy.GlobalRole)
if err != nil {
log.Fatal(“GetAllRoles:”, err)
}
fmt.Printf(“All Roles: %+v\n”, allRoles)

// 获取角色分配情况
assignments, err := client.GetRoleAssignments(rolestrategy.GlobalRole)
if err != nil {
log.Fatal(“GetRoleAssignments:”, err)
}
fmt.Printf(“Assignments: %+v\n”, assignments)

// 获取全局角色名列表
globalRoles, err := client.GetGlobalRoleNames()
if err != nil {
log.Fatal(“GetGlobalRoleNames:”, err)
}
fmt.Println(“Global Roles:”, globalRoles)

// 获取项目角色名列表
projectRoles, err := client.GetProjectRoleNames()
if err != nil {
log.Fatal(“GetProjectRoleNames:”, err)
}
fmt.Println(“Project Roles:”, projectRoles)

// 获取匹配的作业
matchingJobs, err := client.GetMatchingJobs(“dev-.*”, 10)
if err != nil {
log.Fatal(“GetMatchingJobs:”, err)
}
fmt.Printf(“Matching Jobs: %+v\n”, matchingJobs)

// 获取匹配的代理
matchingAgents, err := client.GetMatchingAgents(“agent-.*”, 10)
if err != nil {
log.Fatal(“GetMatchingAgents:”, err)
}
fmt.Printf(“Matching Agents: %+v\n”, matchingAgents)

开发一个 Web UI

我们有了 SDK,现在可以基于 SDK 开发一个简单的 Web UI,我这里后端使用 Gin 框架,前端就简单写个 Html 实现。
对于后端,我们这里只设计了四个接口,它们是:
/api/users:获取用户信息
/api/roles/global:获取全局角色
/api/roles/project:获取项目角色
/api/users/assign:用户授权
完整的后端代码如下:
// main.go
package main

import (
“fmt”
“log”
“net/http”
“strconv”
“strings”

“github.com/gin-gonic/gin”
rolestrategy “github.com/joker-bai/go-role-strategy-sdk”
)

// Request models
type AssignRequest struct {
Username string json:"username" binding:"required"
GlobalRoles []stringjson:"globalRoles"
ProjectRoles []stringjson:"projectRoles"
}

type UserListResponse struct {
Users []UserRoles json:"users"
Total int json:"total"
Page int json:"page"
PageSize int json:"pageSize"
TotalPages int json:"totalPages"
}

type RoleListResponse []string

type UserRoles struct {
Username string json:"username"
GlobalRoles []stringjson:"globalRoles"
ProjectRoles []stringjson:"projectRoles"
}

var jenkinsClient *rolestrategy.Client

func main() {
jenkinsClient = rolestrategy.NewClient(
“http://127.0.0.1:8080/jenkins”,
“admin”,
“your-api-token”,
)

r := gin.Default()
r.StaticFile(“/”, “./web/index.html”)

api := r.Group(“/api”)
{
api.GET(“/users”, getUsersHandler)
api.GET(“/roles/global”, getGlobalRolesHandler)
api.GET(“/roles/project”, getProjectRolesHandler)
api.POST(“/users/assign”, assignRolesHandler)
}

log.Println(“Server starting on :8080”)
r.Run(“:8080”)
}

func getUsersHandler(c gin.Context) { var users []UserRoles userMap := make(map[string]UserRoles)

page, _ := strconv.Atoi(c.DefaultQuery(“page”, “1”))
pageSize, _ := strconv.Atoi(c.DefaultQuery(“pageSize”, “10”))
search := strings.TrimSpace(c.DefaultQuery(“search”, “”))
if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 {
pageSize = 10
}

// 从 Jenkins 获取数据
processRoleMap := func(roleType rolestrategy.RoleType, addToGlobal bool) {
roleMap, err := jenkinsClient.GetAllRoles(roleType)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: fmt.Sprintf(“Failed to get %s roles: %v”, roleType, err)})
c.Abort()
return
}
for roleName, sids := range roleMap {
for , sid := range sids { if sid.Type != “USER” { continue } if , exists := userMap[sid.SID]; !exists {
userMap[sid.SID] = &UserRoles{
Username: sid.SID,
GlobalRoles: []string{},
ProjectRoles: []string{},
}
}
if addToGlobal {
userMap[sid.SID].GlobalRoles = append(userMap[sid.SID].GlobalRoles, roleName)
} else {
userMap[sid.SID].ProjectRoles = append(userMap[sid.SID].ProjectRoles, roleName)
}
}
}
}

processRoleMap(rolestrategy.GlobalRole, true)
if c.IsAborted() {
return
}
processRoleMap(rolestrategy.ProjectRole, false)
if c.IsAborted() {
return
}

for _, user := range userMap {
users = append(users, *user)
}

// 应用搜索过滤
if search != “” {
var filteredUsers []UserRoles
for _, user := range users {
if strings.Contains(strings.ToLower(user.Username), strings.ToLower(search)) {
filteredUsers = append(filteredUsers, user)
}
}
users = filteredUsers
}

total := len(users)
totalPages := (total + pageSize – 1) / pageSize
start := (page – 1) * pageSize
end := start + pageSize
if start >= total {
users = []UserRoles{}
} else {
if end > total {
end = total
}
users = users[start:end]
}

c.JSON(http.StatusOK, UserListResponse{
Users: users,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
})
}

func getGlobalRolesHandler(c *gin.Context) {
roles, err := jenkinsClient.GetGlobalRoleNames()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: err.Error()})
return
}
c.JSON(http.StatusOK, RoleListResponse(roles))
}

func getProjectRolesHandler(c *gin.Context) {
roles, err := jenkinsClient.GetProjectRoleNames()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: err.Error()})
return
}
c.JSON(http.StatusOK, RoleListResponse(roles))
}

func assignRolesHandler(c *gin.Context) {
var req AssignRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{“error”: err.Error()})
return
}

// 获取用户当前的角色
globalMap, err := jenkinsClient.GetAllRoles(rolestrategy.GlobalRole)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: “Failed to get current global roles: ” + err.Error()})
return
}
projectMap, err := jenkinsClient.GetAllRoles(rolestrategy.ProjectRole)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: “Failed to get current project roles: ” + err.Error()})
return
}

// 构建当前角色集合
currentGlobalRoles := make(map[string]bool)
for roleName, sids := range globalMap {
for _, sid := range sids {
if sid.Type == “USER” && sid.SID == req.Username {
currentGlobalRoles[roleName] = true
}
}
}

currentProjectRoles := make(map[string]bool)
for roleName, sids := range projectMap {
for _, sid := range sids {
if sid.Type == “USER” && sid.SID == req.Username {
currentProjectRoles[roleName] = true
}
}
}

// 分配新角色
for _, roleName := range req.GlobalRoles {
if !currentGlobalRoles[roleName] {
if err := jenkinsClient.AssignUserRole(rolestrategy.GlobalRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: “Failed to assign global role: ” + err.Error()})
return
}
}
}

for _, roleName := range req.ProjectRoles {
if !currentProjectRoles[roleName] {
if err := jenkinsClient.AssignUserRole(rolestrategy.ProjectRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: “Failed to assign project role: ” + err.Error()})
return
}
}
}

// 移除不再拥有的角色
for roleName := range currentGlobalRoles {
if !contains(req.GlobalRoles, roleName) {
if err := jenkinsClient.UnassignUserRole(rolestrategy.GlobalRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: “Failed to unassign global role: ” + err.Error()})
return
}
}
}

for roleName := range currentProjectRoles {
if !contains(req.ProjectRoles, roleName) {
if err := jenkinsClient.UnassignUserRole(rolestrategy.ProjectRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{“error”: “Failed to unassign project role: ” + err.Error()})
return
}
}
}

c.JSON(http.StatusOK, gin.H{“success”: true, “message”: “Roles updated successfully”})
}

func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
returntrue
}
}
returnfalse
}

前端代码就不写了,直接到 仓库 查看。
实际的效果如下:
ff05662851d9db0302a62ec564a96b5b MD5
ff05662851d9db0302a62ec564a96b5b MD5
用户详情,可以查看目前该用户拥有的权限:
971f6b099af019c29ed8856a7b1bcb5d MD5
971f6b099af019c29ed8856a7b1bcb5d MD5
点击修改,可以直接修改用户的权限:
91dcad6460cbdb0c730c3a3843cce753 MD5
91dcad6460cbdb0c730c3a3843cce753 MD5
对于新用户,也可以直接授权:
7ac7f6d1b3f1541474cae991a45adef6 MD5
7ac7f6d1b3f1541474cae991a45adef6 MD5
这样就不用害怕 Jenkins 卡死了。同时,我也将代码传到了 Github,有兴趣的可以了解一下。
上面的代码仅仅是一个简单的 demo,可以对其进行扩充,比如:
用户接入企业用户中心,判断该用户是否存在
增加多Jenkins管理,操作者可以切换不同的Jenkins进行授权操作
当然,目前的界面也比较丑,不过,就这样吧……

最后,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开发故事」。

如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!

你还可以把我的公众号设为「星标」,这样当公众号文章更新时,你会在第一时间收到推送消息,避免错过我的文章更新。

我是 乔克,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!

声明:来自运维开发故事,仅代表创作者观点。链接:https://eyangzhen.com/2764.html

运维开发故事的头像运维开发故事

相关推荐

关注我们
关注我们
购买服务
购买服务
返回顶部