diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2055531 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +tmp +logs +public/static/dist \ No newline at end of file diff --git a/.github/workflows/docker-image.yml b/.github/workflows/build-docker-image.yml similarity index 100% rename from .github/workflows/docker-image.yml rename to .github/workflows/build-docker-image.yml diff --git a/.github/workflows/go-binary-release.yml b/.github/workflows/buildAndPush-binary-to-release.yml similarity index 68% rename from .github/workflows/go-binary-release.yml rename to .github/workflows/buildAndPush-binary-to-release.yml index 555dc1e..d7f0223 100644 --- a/.github/workflows/go-binary-release.yml +++ b/.github/workflows/buildAndPush-binary-to-release.yml @@ -1,4 +1,4 @@ -name: build +name: build and push binary to release on: release: @@ -16,6 +16,8 @@ jobs: goos: windows steps: - uses: actions/checkout@v3 + - run: | + release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static - uses: wangyoucao577/go-release-action@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} # 一个默认的变量,用来实现往 Release 中添加文件 @@ -23,4 +25,4 @@ jobs: goarch: ${{ matrix.goarch }} goversion: 1.18 # 可以指定编译使用的 Golang 版本 binary_name: "go-ldap-admin" # 可以指定二进制文件的名称 - extra_files: LICENSE config.yml go-ldap-admin-priv.pem go-ldap-admin-pub.pem rbac_model.conf README.md # 需要包含的额外文件 \ No newline at end of file + extra_files: LICENSE config.yml README.md # 需要包含的额外文件 \ No newline at end of file diff --git a/.github/workflows/toc.yml b/.github/workflows/generate-toc.yml similarity index 100% rename from .github/workflows/toc.yml rename to .github/workflows/generate-toc.yml diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci-check.yml similarity index 53% rename from .github/workflows/go-ci.yml rename to .github/workflows/go-ci-check.yml index f89c9df..1031aac 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci-check.yml @@ -11,16 +11,20 @@ jobs: with: go-version: 1.18 - uses: actions/checkout@v3 + - run: | + release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.47.3 + version: v1.57.2 args: --timeout=5m --skip-files="public/client/feishu/feishu.go" build: name: go-build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - run: | + release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static - name: Set up Go uses: actions/setup-go@v3 with: diff --git a/.github/workflows/issue.yml b/.github/workflows/issue-bot.yml similarity index 100% rename from .github/workflows/issue.yml rename to .github/workflows/issue-bot.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release-drafter.yml similarity index 100% rename from .github/workflows/release.yml rename to .github/workflows/release-drafter.yml diff --git a/.gitignore b/.gitignore index 0912046..c5274f0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ go-ldap-admin.db # Dependency directories (remove the comment below to include it) # vendor/ tmp -docs/docker-compose/data \ No newline at end of file +docs/docker-compose/data +dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index dd933f2..0328438 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,29 @@ -FROM golang:1.18.10-alpine3.16 AS builder +FROM registry.cn-hangzhou.aliyuncs.com/ali_eryajf/golang:1.18.10-alpine3.17 AS builder -# ENV GOPROXY https://goproxy.io +WORKDIR /app -RUN mkdir /app && apk add --no-cache --virtual .build-deps \ - ca-certificates \ - gcc \ - g++ +RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories \ + && apk upgrade && apk add --no-cache --virtual .build-deps \ + ca-certificates gcc g++ curl -ADD . /app/ +ADD . . -WORKDIR /app +RUN release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static RUN sed -i 's@localhost:389@openldap:389@g' /app/config.yml \ && sed -i 's@host: localhost@host: mysql@g' /app/config.yml && go build -o go-ldap-admin . ### build final image -FROM alpine:3.16 - -# we set the timezone `Asia/Shanghai` by default, you can be modified -# by `docker build --build-arg="TZ=Other_Timezone ..."` -ARG TZ="Asia/Shanghai" +FROM registry.cn-hangzhou.aliyuncs.com/ali_eryajf/alpine:3.19 -ENV TZ ${TZ} - -RUN mkdir /app +LABEL maintainer eryajf@163.com WORKDIR /app -COPY --from=builder /app/ . - - -RUN apk upgrade \ - && apk add bash tzdata sqlite vim \ - && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \ - && echo ${TZ} > /etc/timezone +COPY --from=builder /app/wait . +COPY --from=builder /app/LICENSE . +COPY --from=builder /app/config.yml . +COPY --from=builder /app/go-ldap-admin . RUN chmod +x wait go-ldap-admin diff --git a/config.yml b/config.yml index 7602fd1..2b045a9 100644 --- a/config.yml +++ b/config.yml @@ -8,10 +8,6 @@ system: port: 8888 # 是否初始化数据(没有初始数据时使用, 已发布正式版改为false) init-data: true - # rsa公钥文件路径(config.yml相对路径, 也可以填绝对路径) - rsa-public-key: go-ldap-admin-pub.pem - # rsa私钥文件路径(config.yml相对路径, 也可以填绝对路径) - rsa-private-key: go-ldap-admin-priv.pem logs: # 日志等级(-1:Debug, 0:Info, 1:Warn, 2:Error, 3:DPanic, 4:Panic, 5:Fatal, -1<=level<=5, 参照zap.level源码) @@ -55,11 +51,6 @@ mysql: # 字符集(utf8mb4_general_ci速度比utf8mb4_unicode_ci快些) collation: utf8mb4_general_ci -# casbin配置 -casbin: - # 模型配置文件, config.yml相对路径 - model-path: 'rbac_model.conf' - # jwt配置 jwt: # jwt标识 diff --git a/config/config.go b/config/config.go index 0b355c2..7b0be81 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config import ( + _ "embed" "fmt" "os" @@ -15,12 +16,18 @@ import ( // 全局配置变量 var Conf = new(config) +//go:embed go-ldap-admin-priv.pem +var priv []byte + +//go:embed go-ldap-admin-pub.pem +var pub []byte + type config struct { - System *SystemConfig `mapstructure:"system" json:"system"` - Logs *LogsConfig `mapstructure:"logs" json:"logs"` - Database *Database `mapstructure:"database" json:"database"` - Mysql *MysqlConfig `mapstructure:"mysql" json:"mysql"` - Casbin *CasbinConfig `mapstructure:"casbin" json:"casbin"` + System *SystemConfig `mapstructure:"system" json:"system"` + Logs *LogsConfig `mapstructure:"logs" json:"logs"` + Database *Database `mapstructure:"database" json:"database"` + Mysql *MysqlConfig `mapstructure:"mysql" json:"mysql"` + // Casbin *CasbinConfig `mapstructure:"casbin" json:"casbin"` Jwt *JwtConfig `mapstructure:"jwt" json:"jwt"` RateLimit *RateLimitConfig `mapstructure:"rate-limit" json:"rateLimit"` Ldap *LdapConfig `mapstructure:"ldap" json:"ldap"` @@ -50,8 +57,8 @@ func InitConfig() { panic(fmt.Errorf("初始化配置文件失败:%s", err)) } // 读取rsa key - Conf.System.RSAPublicBytes = RSAReadKeyFromFile(Conf.System.RSAPublicKey) - Conf.System.RSAPrivateBytes = RSAReadKeyFromFile(Conf.System.RSAPrivateKey) + Conf.System.RSAPublicBytes = pub + Conf.System.RSAPrivateBytes = priv }) if err != nil { @@ -62,27 +69,9 @@ func InitConfig() { panic(fmt.Errorf("初始化配置文件失败:%s", err)) } // 读取rsa key - Conf.System.RSAPublicBytes = RSAReadKeyFromFile(Conf.System.RSAPublicKey) - Conf.System.RSAPrivateBytes = RSAReadKeyFromFile(Conf.System.RSAPrivateKey) - -} - -// 从文件中读取RSA key -func RSAReadKeyFromFile(filename string) []byte { - f, err := os.Open(filename) - var b []byte + Conf.System.RSAPublicBytes = pub + Conf.System.RSAPrivateBytes = priv - if err != nil { - return b - } - defer f.Close() - fileInfo, _ := f.Stat() - b = make([]byte, fileInfo.Size()) - _, err = f.Read(b) - if err != nil { - return b - } - return b } type SystemConfig struct { @@ -90,8 +79,6 @@ type SystemConfig struct { UrlPathPrefix string `mapstructure:"url-path-prefix" json:"urlPathPrefix"` Port int `mapstructure:"port" json:"port"` InitData bool `mapstructure:"init-data" json:"initData"` - RSAPublicKey string `mapstructure:"rsa-public-key" json:"rsaPublicKey"` - RSAPrivateKey string `mapstructure:"rsa-private-key" json:"rsaPrivateKey"` RSAPublicBytes []byte `mapstructure:"-" json:"-"` RSAPrivateBytes []byte `mapstructure:"-" json:"-"` } @@ -123,9 +110,9 @@ type MysqlConfig struct { Collation string `mapstructure:"collation" json:"collation"` } -type CasbinConfig struct { - ModelPath string `mapstructure:"model-path" json:"modelPath"` -} +// type CasbinConfig struct { +// ModelPath string `mapstructure:"model-path" json:"modelPath"` +// } type JwtConfig struct { Realm string `mapstructure:"realm" json:"realm"` diff --git a/go-ldap-admin-priv.pem b/config/go-ldap-admin-priv.pem similarity index 100% rename from go-ldap-admin-priv.pem rename to config/go-ldap-admin-priv.pem diff --git a/go-ldap-admin-pub.pem b/config/go-ldap-admin-pub.pem similarity index 100% rename from go-ldap-admin-pub.pem rename to config/go-ldap-admin-pub.pem diff --git a/docs/docker-compose/docker-compose.yaml b/docs/docker-compose/docker-compose.yaml index 5847198..d5eddd9 100644 --- a/docs/docker-compose/docker-compose.yaml +++ b/docs/docker-compose/docker-compose.yaml @@ -72,7 +72,6 @@ services: hostname: go-ldap-admin-server restart: always environment: - TZ: Asia/Shanghai WAIT_HOSTS: mysql:3306, openldap:389 ports: - 8888:8888 @@ -86,19 +85,3 @@ services: - openldap:go-ldap-admin-openldap # ldap容器的 service_name:container_name networks: - go-ldap-admin - - go-ldap-admin-ui: - image: registry.cn-hangzhou.aliyuncs.com/ali_eryajf/go-ldap-admin-ui - container_name: go-ldap-admin-ui - hostname: go-ldap-admin-ui - restart: always - environment: - TZ: Asia/Shanghai - ports: - - 8090:80 - depends_on: - - go-ldap-admin-server - links: - - go-ldap-admin-server:go-ldap-admin-server - networks: - - go-ldap-admin \ No newline at end of file diff --git a/main.go b/main.go index f497c7f..0784e6e 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,7 @@ func main() { // 启动定时任务 logic.InitCron() - common.Log.Info(fmt.Sprintf("Server is running at %s:%d/%s", host, port, config.Conf.System.UrlPathPrefix)) + common.Log.Info(fmt.Sprintf("Server is running at http://%s:%d", host, port)) // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. diff --git a/middleware/EmbedMiddleware.go b/middleware/EmbedMiddleware.go new file mode 100644 index 0000000..72fd2c5 --- /dev/null +++ b/middleware/EmbedMiddleware.go @@ -0,0 +1,91 @@ +package middleware + +import ( + "embed" + "io/fs" + "net/http" + "os" + "path" + "strings" + + "github.com/gin-gonic/gin" +) + +const INDEX = "index.html" + +type ServeFileSystem interface { + http.FileSystem + Exists(prefix string, path string) bool +} + +type localFileSystem struct { + http.FileSystem + root string + indexes bool +} + +func LocalFile(root string, indexes bool) *localFileSystem { + return &localFileSystem{ + FileSystem: gin.Dir(root, indexes), + root: root, + indexes: indexes, + } +} + +func (l *localFileSystem) Exists(prefix string, filepath string) bool { + if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) { + name := path.Join(l.root, p) + stats, err := os.Stat(name) + if err != nil { + return false + } + if stats.IsDir() { + if !l.indexes { + index := path.Join(name, INDEX) + _, err := os.Stat(index) + if err != nil { + return false + } + } + } + return true + } + return false +} + +func ServeRoot(urlPrefix, root string) gin.HandlerFunc { + return Serve(urlPrefix, LocalFile(root, false)) +} + +// Static returns a middleware handler that serves static files in the given directory. +func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc { + fileserver := http.FileServer(fs) + if urlPrefix != "" { + fileserver = http.StripPrefix(urlPrefix, fileserver) + } + return func(c *gin.Context) { + if fs.Exists(urlPrefix, c.Request.URL.Path) { + fileserver.ServeHTTP(c.Writer, c.Request) + c.Abort() + } + } +} + +type embedFileSystem struct { + http.FileSystem +} + +func (e embedFileSystem) Exists(prefix string, path string) bool { + _, err := e.Open(path) + return err == nil +} + +func EmbedFolder(fsEmbed embed.FS, targetPath string) ServeFileSystem { + fsys, err := fs.Sub(fsEmbed, targetPath) + if err != nil { + panic(err) + } + return embedFileSystem{ + FileSystem: http.FS(fsys), + } +} diff --git a/public/common/casbin.go b/public/common/casbin.go index f10c4d6..6582458 100644 --- a/public/common/casbin.go +++ b/public/common/casbin.go @@ -3,9 +3,8 @@ package common import ( "fmt" - "github.com/eryajf/go-ldap-admin/config" - "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" gormadapter "github.com/casbin/gorm-adapter/v3" ) @@ -24,12 +23,33 @@ func InitCasbinEnforcer() { Log.Info("初始化Casbin完成!") } +var casbinModel = ` +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*") +` + func mysqlCasbin() (*casbin.Enforcer, error) { a, err := gormadapter.NewAdapterByDB(DB) if err != nil { return nil, err } - e, err := casbin.NewEnforcer(config.Conf.Casbin.ModelPath, a) + m, err := model.NewModelFromString(casbinModel) + if err != nil { + return nil, err + } + e, err := casbin.NewEnforcer(m, a) if err != nil { return nil, err } diff --git a/public/static/static.go b/public/static/static.go new file mode 100644 index 0000000..103a94d --- /dev/null +++ b/public/static/static.go @@ -0,0 +1,6 @@ +package static + +import "embed" + +//go:embed all:dist +var Static embed.FS diff --git a/rbac_model.conf b/rbac_model.conf deleted file mode 100644 index 1686f0b..0000000 --- a/rbac_model.conf +++ /dev/null @@ -1,14 +0,0 @@ -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act - -[role_definition] -g = _, _ - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*") \ No newline at end of file diff --git a/routes/a_routes.go b/routes/a_routes.go index 674b9cc..b40b012 100644 --- a/routes/a_routes.go +++ b/routes/a_routes.go @@ -2,11 +2,13 @@ package routes import ( "fmt" + "net/http" "time" "github.com/eryajf/go-ldap-admin/config" "github.com/eryajf/go-ldap-admin/middleware" "github.com/eryajf/go-ldap-admin/public/common" + "github.com/eryajf/go-ldap-admin/public/static" "github.com/gin-gonic/gin" ) @@ -23,6 +25,16 @@ func InitRoutes() *gin.Engine { // r := gin.New() // r.Use(gin.Recovery()) + r.Use(middleware.Serve("/", middleware.EmbedFolder(static.Static, "dist"))) + r.NoRoute(func(c *gin.Context) { + data, err := static.Static.ReadFile("dist/index.html") + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + c.Data(http.StatusOK, "text/html; charset=utf-8", data) + }) + // 启用限流中间件 // 默认每50毫秒填充一个令牌,最多填充200个 fillInterval := time.Duration(config.Conf.RateLimit.FillInterval) diff --git a/test/README.md b/test/README.md deleted file mode 100644 index b7be79a..0000000 --- a/test/README.md +++ /dev/null @@ -1,2 +0,0 @@ - -> 因为一些测试场景需要依赖配置的初始化,因此这里单独一个目录放类似的测试 \ No newline at end of file diff --git a/test/config.yml b/test/config.yml deleted file mode 120000 index 1555250..0000000 --- a/test/config.yml +++ /dev/null @@ -1 +0,0 @@ -../config.yml \ No newline at end of file diff --git a/test/go-ldap-admin-priv.pem b/test/go-ldap-admin-priv.pem deleted file mode 120000 index bb7042f..0000000 --- a/test/go-ldap-admin-priv.pem +++ /dev/null @@ -1 +0,0 @@ -../go-ldap-admin-priv.pem \ No newline at end of file diff --git a/test/go-ldap-admin-pub.pem b/test/go-ldap-admin-pub.pem deleted file mode 120000 index 712a71b..0000000 --- a/test/go-ldap-admin-pub.pem +++ /dev/null @@ -1 +0,0 @@ -../go-ldap-admin-pub.pem \ No newline at end of file diff --git a/test/isql_test.go b/test/isql_test.go deleted file mode 100644 index 5d52a53..0000000 --- a/test/isql_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package test - -import ( - "fmt" - "testing" - - "github.com/eryajf/go-ldap-admin/config" - "github.com/eryajf/go-ldap-admin/public/common" - "github.com/eryajf/go-ldap-admin/public/tools" - "github.com/eryajf/go-ldap-admin/service/isql" -) - -func InitConfig() { - // 加载配置文件到全局配置结构体 - config.InitConfig() - - // 初始化日志 - common.InitLogger() - - // 初始化数据库(mysql) - common.InitDB() - - // 初始化ldap连接 - common.InitLDAP() - - // 初始化casbin策略管理器 - common.InitCasbinEnforcer() - - // 初始化Validator数据校验 - common.InitValidate() -} - -func TestUserExist(t *testing.T) { - InitConfig() - - var u isql.UserService - filter := tools.H{ - "id": "111", - } - - if u.Exist(filter) { - fmt.Println("用户名已存在") - } else { - fmt.Println("用户名不存在") - } -} diff --git a/test/rbac_model.conf b/test/rbac_model.conf deleted file mode 120000 index de109ac..0000000 --- a/test/rbac_model.conf +++ /dev/null @@ -1 +0,0 @@ -../rbac_model.conf \ No newline at end of file diff --git a/test/tools_test.go b/test/tools_test.go deleted file mode 100644 index 92d52a2..0000000 --- a/test/tools_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package test - -import ( - "fmt" - "testing" - - "github.com/eryajf/go-ldap-admin/config" - "github.com/eryajf/go-ldap-admin/public/tools" -) - -func TestUnGenPassword(t *testing.T) { - InitConfig() - pass := "$2a$10$FlzrnJeE3Ad8uokvSAl/gunkRZsdREwlFZZqPcwfkekXOc9oAa9KS" - fmt.Printf("秘钥为:%s\n", config.Conf.System.RSAPrivateBytes) - // 密码通过RSA解密 - decodeData, err := tools.RSADecrypt([]byte(pass), config.Conf.System.RSAPrivateBytes) - if err != nil { - fmt.Printf("密码解密失败:%s\n", err) - } - fmt.Printf("密码解密后为:%s\n", string(decodeData)) -}